本节思维导图:
线程池 提供一种限制和管理资源(包括执行一个任务)。每个线程池还维护一些基本统计信息,例如已完成任务的数量。
这里借用《Java并发编程的艺术》 提到的来说一下使用线程池的好处。
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的开销。提高响应速度。当任务可达时,任务可以不需要等到线程创建就能立即执行。提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控。Executor 框架是Java5 之后引进的,在Java5 之后,通过Executor 来启动线程比使用 Thread 的start 方式更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
补充:this逃逸 是指在构造函数返回之前,其他线程就持有该对象的饮用。调用尚未构造完全的对象方法可能引发令人疑惑的错误。
执行任务需要实现的 Runnable接口或Callable接口。
Runable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadoolExecutor执行。
两者的区别:
Runnable接口不会返回结果,但是Callable接口可以返回结果。后面介绍Executor类的一些方法的时候会介绍到两者的相互转换。
如下图所示,包括认为执行机制的核心接口Executor,以及继承自Executor 接口的ExecutorService接口。ScheduledThreadPoolExecutor和ThreadPoolExecutor这两个关键类实现了ExecutorService接口。
注意:通过查看ScheduledThreadPoolExecutor源代码可以发现,ScheduledThreadPoolExecutor实际上是继承了ThreadPoolExecutor,并实现了SchduledExecutorService,而ScheduledExecutorService又实现了ExecutorService,正如下面的类图:
ThreadPoolExecutor类描述:
* @since 1.5 * @author Doug Lea */ public class ThreadPoolExecutor extends AbstractExecutorService {ScheduledThreadPoolExecutor类描述:
* @since 1.5 * @author Doug Lea */ public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {类关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UNmsuiZG-1572841549196)(C:\Users\hbing\AppData\Roaming\Typora\typora-user-images\1572594208514.png)]
Future接口 以及 Future接口的实现类 FutureTask类。
当我们把Runnable接口或Callable接口的实现类提交(调用submit方法)给ThreadPoolExecutor或ScheduledPoolExecutor时,会返回一个FutureTask对象。
我们以AbstractExecutorService接口中的一个submit方法为例子来看看源代码:
public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; }进入newTaskFor方法,看看返回值。
* @since 1.6 */ protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { return new FutureTask<T>(runnable, value); }返回的是一个FutureTask对象。
主线程首先要创建实现Runnable或者Callable接口的任务对象。
工具类Executors 可以实现Runnable对象和Callable对象之间的相互转换。
public static Callable<Object> callable(Runnable task) { public static <T> Callable<T> callable(Runnable task, T result) {然后可以把创建完成的Runnable对象直接交给ExecutorService执行
ThreadTest task = new ThreadTest(); Callable callable = Executors.callable(task); ExecutorService executorService = Executors.newFixedThreadPool(5); executorService.execute(task); Future future1 = executorService.submit(task); FutureTask future2 = (FutureTask) executorService.submit(callable); boolean isDone = future1.isDone();执行execute()方法和submit()方法的区别是什么呢?
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;**submit()方法用于提交需要返回值的任务,线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否成功执行,**并且可以通过future的get()方法获取返回值,get()方法会阻塞当前线程直到任务完成,而get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务还没有完成。如果执行ExecutorService.submit(。。。),ExecutorService将会返回一个实现Future接口对象(到目前为止的JDK中,返回的都是FutureTask对象)。由于FutureTask实现了Runnable接口,也可以创建FutureTask,然后交给ExecutorService去执行。
最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消任务的执行。
线程池实现类ThreadPoolExecutor是Executor 框架最核心的类,来看看比较重要的四个属性:
我们最常看到这个方法,其余三个都是在这个构造方法的基础上产生的
/** * 用给定的初始参数创建一个新的ThreadPoolExecutor。 * @param keepAliveTime 当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提 交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime; * @param unit keepAliveTime参数的时间单位 * * @param workQueue 等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时 候,把该任务封装成一个Worker对象放入等待队列; * * @param threadFactory 执行者创建新线程时使用的工厂 * * @param handler RejectedExecutionHandler类型的变量,表示线程池的饱和策略。 * * 如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。 * 线程池提供了4种策略: 1.AbortPolicy:直接抛出异常,这是默认策略; 2.CallerRunsPolicy:用调用者所在的线程来执行任务; 3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务; 4.DiscardPolicy:直接丢弃任务; */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }在《阿里巴巴Java开发手册》“并发处理”这一章节,明确指出线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。
为什么呢?
使用线程池的好处是 减少在创建和销毁线程上锁消耗的时间以及系统资源开销,解决资源不足的问题。如果不是用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“切换过度”的问题。
另外《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
Executors 返回线程池对象的弊端如下:
FixedThreadPool 和 SingleThreadPoolExecutor :允许请求的队列长度为 Integer.MAX_VALUE;可能堆积大量的请求,从而导致OOM;
CachedThreadPool 和 ScheduledThreadPool :允许创建的线程数Integer.MAX_VALUE;可能会创建大量线程,从而导致OOM;
方式一:通过构造方法实现
方式二:通过Executor 框架的工具类Executors来实现
我们可以创建三种类型的ThreadPoolExecutor:
FixedThreadPoolSingleThreadExecutorCachedThreadPool对应Executors工具类中的方法如图所示:
FixedThreadPool 被称为可重用固定数量的线程池。通过Executors类的源代码来看一下实现:
/** * 创建一个可重用固定数量线程的线程池 *在任何时候至多有n个线程处于活动状态 *如果在所有线程处于活动状态时提交其他任务,则它们将在队列中等待, *直到线程可用。 如果任何线程在关闭之前的执行期间由于失败而终止, *如果需要执行后续任务,则一个新的线程将取代它。池中的线程将一直存在 *知道调用shutdown方法 * @param nThreads 线程池中的线程数 * @param threadFactory 创建新线程时使用的factory * @return 新创建的线程池 * @throws NullPointerException 如果threadFactory为null * @throws IllegalArgumentException if {@code nThreads <= 0} */ public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }另外还有一个FixedThreadPool的实现方法,和上面的类似,所以这里不多做阐述:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }从上面的源代码可以看出,新创建的FixedThreadPool的corePoolSize 和 maximumPoolSize一样,都被设置为nThreads。
FixedThreadPool的execute()方法运行示意图(该图片来源:《Java并发编程的艺术》):
上图说明:
如果当前运行的线程数小于corePoolSize,则创建新的线程来执行任务。当前运行的线程数等于corePoolSize,将任务加入LinkedBlockingQueue。线程执行完1中的任务后,会循环反复从LinkedBlockingQueue中获取任务来执行。FixedThreadPool使用无界队列 LinkedBlockingQueue (队列的容量为Integer.MAX_VALUE)
public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }作为线程池的工作队列会对线程池带来如下的影响:
当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。由于1,使用无界队列是maximumPoolSize将是一个无效参数。由于1和2,使用无界队列是keepAliveTime将是一个无效参数。运行中的FixedThreadPool(未执行shutdown() 或 shutdownnow()方法)不会拒绝任务。SingleThreadExecutor是使用单个worker线程的Executor。下面看看SingleThreadExecutor的实现:
/** *创建使用单个worker线程运行无界队列的Executor *并使用提供的ThreadFactory在需要时创建新线程 * * @param threadFactory 创建新线程时使用的factory * * @return 新创建的单线程Executor * @throws NullPointerException 如果ThreadFactory为空 */ public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } ① 新创建的SingleThreadExecutor 的corePoolSize和maximumPoolSize都被设置为1.其他参数和
FixedThreadPool相同。
② SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为
Intger.MAX_VALUE)。
SingleThreadExecutor的运行示意图(该图片来源:《Java并发编程的艺术》):
上图说明;
如果当前运行的线程数少于corePoolSize,则创建一个新的线程执行任务。当前线程池中有一个运行的线程后,新任务加入到 LinkedBlockingQueue。线程执行完1中的任务后,会循环反复从 LinkedBlockingQueue中获取任务来执行。cachedThreadPool是一个会根据需要创建新线程的线程池。通过源码来看看CachedThreadPool的实现:
/** * 创建一个线程池,根据需要创建新线程,但会在先前构建的线程可用时重用它, *并在需要时使用提供的ThreadFactory创建新线程。 * @param threadFactory 创建新线程使用的factory * @return 新创建的线程池 * @throws NullPointerException 如果threadFactory为空 */ public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }CachedThreadPool的corePoolSize被设置为空(0),maximumPoolSize被设置为Integer.MAX.VALUE,即它是无界的。
因为是无界的,也就意味着如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新的线程。极端情况下,这样会导致耗尽cpu和内存资源。
CachedThreadPool的execute()方法的执行示意图(该图片来源:《Java并发编程的艺术》):
上图说明:
首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有闲置线程正在执行SynchrinousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成,否则执行下面步骤2当初始maximumPool为空,或者maximumPool中没有空闲线程,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤1将失败,此时CachedThreadPool会创建新线程执行任务,execute()方法执行完成。首先创建一个Runnable接口的实现类(当然也可以是Callable接口,我们上面已经说了两者的区别是:Runnable接口不会返回结果但是Callable接口会返回结果。后面介绍Executors类的一些方法的时候,会介绍到两者之间的转换)。
import java.util.Date; /** * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。 */ public class WorkerThread implements Runnable { private String command; public WorkerThread(String s) { this.command = s; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date()); processCommand(); System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date()); } private void processCommand() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return this.command; } }编写测试程序,我们这里以FixedThreadPool为例子
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExecutorDemo { public static void main(String[] args) { //创建一个FixedThreadPool对象 ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { //创建WorkerThread对象(WorkerThread类实现了Runnable 接口) Runnable worker = new WorkerThread("" + i); //执行Runnable executor.execute(worker); } //终止线程池 executor.shutdown(); while (!executor.isTerminated()) { } System.out.println("Finished all threads"); } }运行结果:
pool-1-thread-5 Start. Time = Thu May 31 10:22:52 CST 2018 pool-1-thread-3 Start. Time = Thu May 31 10:22:52 CST 2018 pool-1-thread-2 Start. Time = Thu May 31 10:22:52 CST 2018 pool-1-thread-4 Start. Time = Thu May 31 10:22:52 CST 2018 pool-1-thread-1 Start. Time = Thu May 31 10:22:52 CST 2018 pool-1-thread-4 End. Time = Thu May 31 10:22:57 CST 2018 pool-1-thread-1 End. Time = Thu May 31 10:22:57 CST 2018 pool-1-thread-2 End. Time = Thu May 31 10:22:57 CST 2018 pool-1-thread-5 End. Time = Thu May 31 10:22:57 CST 2018 pool-1-thread-3 End. Time = Thu May 31 10:22:57 CST 2018 pool-1-thread-5 Start. Time = Thu May 31 10:22:57 CST 2018 pool-1-thread-2 Start. Time = Thu May 31 10:22:57 CST 2018 pool-1-thread-1 Start. Time = Thu May 31 10:22:57 CST 2018 pool-1-thread-4 Start. Time = Thu May 31 10:22:57 CST 2018 pool-1-thread-3 Start. Time = Thu May 31 10:22:57 CST 2018 pool-1-thread-5 End. Time = Thu May 31 10:23:02 CST 2018 pool-1-thread-1 End. Time = Thu May 31 10:23:02 CST 2018 pool-1-thread-2 End. Time = Thu May 31 10:23:02 CST 2018 pool-1-thread-3 End. Time = Thu May 31 10:23:02 CST 2018 pool-1-thread-4 End. Time = Thu May 31 10:23:02 CST 2018 Finished all threadsshutdown() 方法表明关闭已在Executor上调用,因为不会在想DelayedPool添加任何其他任务(由于ScheduledThreadPoolExecutor类在内部使用)。但是,已经在队列中提交的任务将被运行执行完成。
shutdownNow()方法试图终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的List。
isTerminated()表示执行程序正在关闭,但并非所有任务都已经完成执行。
isShutdown()表示所有线程都已完成执行。
ScheduledThreadPoolExecutor主要用来在给定的延迟后运行任务,或者定期执行任务。
ScheduledThreadPoolExecutor使用的任务队列DelayQueue封装了一个PriorityQueue,PriorityQueue会对队列中的任务进行排序,执行所需时间端的放在前面先执行(ScheduledFutureTask的time变量小的先执行),如果执行所需时间相同则先提交的任务将被先执行(ScheduledFutureTask的squenceNumber变量小的先执行)。
ScheduledThreadPoolExecutor和Timer的比较:
Timer对系统时钟的变化敏感,ScheduledThreadPoolExecutor不是Timer只是一个执行线程,因此长时间运行的任务可以延迟其他任务,ScheduledThreadPoolExecutor可以配置任意数量的线程。此外,如果你想提供ThreadFactory,可以完全控制线程的创建。在TimerTask中抛出的运行时异常会杀死一个线程,从而导致Timer死机,即计划任务将不再运行。ScheduledThreadPoolExecutor不仅捕获运行时异常,还允许你在需要时处理它们(通过重写afterExecute方法)。抛出异常的任务将被取消,但其他任务将继续运行。综上,在JDK1.5之后,你没有理由再使用Timer进行任务调度了。
**备注:**Quartz是一个由java编写的任务调度库,由OpenSymphony组织开源出来。在实际项目开发中使用Quartz的还是居多,比较推荐Quartz。因为Quartz理论上能够同时对上万个任务进行调度,拥有丰富的功能特性,包括任务调度、任务持久化、可集群化、插件等待。
ScheduledThreadPoolExecutor的执行主要分为两大部分:
当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask。线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下修改:
使用DelayedQueue作为任务队列获取任务的方式不同执行周期任务后,增加了额外的处理运行结果:
Current Time = Wed May 30 17:11:16 CST 2018 pool-1-thread-1 Start. Time = Wed May 30 17:11:27 CST 2018 pool-1-thread-2 Start. Time = Wed May 30 17:11:28 CST 2018 pool-1-thread-3 Start. Time = Wed May 30 17:11:29 CST 2018 pool-1-thread-1 End. Time = Wed May 30 17:11:32 CST 2018 pool-1-thread-2 End. Time = Wed May 30 17:11:33 CST 2018 pool-1-thread-3 End. Time = Wed May 30 17:11:34 CST 2018 Current Time = Wed May 30 17:11:49 CST 2018 Finished all threads我们可以使用ScheduledExecutorService scheduleAtFixedRate()方法 初始延迟后,以固有频率执行任务(不受任务执行的影响,到时间它就执行)。
时间段是从池中第一个线程开始,因此如果你将period指定为1秒并且线程运行5秒,那么只要第一个工作线程完成执行后,下一个线程就会开始执行。
for (int i = 0; i < 3; i++) { Thread.sleep(1000); WorkerThread worker = new WorkerThread("do heavy processing"); // schedule task to execute at fixed rate scheduledThreadPool.scheduleAtFixedRate(worker, 0, 10, TimeUnit.SECONDS); }运行结果:
Current Time = Wed May 30 17:47:09 CST 2018 pool-1-thread-1 Start. Time = Wed May 30 17:47:10 CST 2018 pool-1-thread-2 Start. Time = Wed May 30 17:47:11 CST 2018 pool-1-thread-3 Start. Time = Wed May 30 17:47:12 CST 2018 pool-1-thread-1 End. Time = Wed May 30 17:47:15 CST 2018 pool-1-thread-2 End. Time = Wed May 30 17:47:16 CST 2018 pool-1-thread-3 End. Time = Wed May 30 17:47:17 CST 2018 pool-1-thread-1 Start. Time = Wed May 30 17:47:20 CST 2018 pool-1-thread-4 Start. Time = Wed May 30 17:47:21 CST 2018 pool-1-thread-2 Start. Time = Wed May 30 17:47:22 CST 2018 pool-1-thread-1 End. Time = Wed May 30 17:47:25 CST 2018 pool-1-thread-4 End. Time = Wed May 30 17:47:26 CST 2018 pool-1-thread-2 End. Time = Wed May 30 17:47:27 CST 2018 pool-1-thread-1 Start. Time = Wed May 30 17:47:30 CST 2018 pool-1-thread-3 Start. Time = Wed May 30 17:47:31 CST 2018 pool-1-thread-5 Start. Time = Wed May 30 17:47:32 CST 2018 pool-1-thread-1 End. Time = Wed May 30 17:47:35 CST 2018 pool-1-thread-3 End. Time = Wed May 30 17:47:36 CST 2018 pool-1-thread-5 End. Time = Wed May 30 17:47:37 CST 2018 pool-1-thread-1 Start. Time = Wed May 30 17:47:40 CST 2018 pool-1-thread-2 Start. Time = Wed May 30 17:47:41 CST 2018 Current Time = Wed May 30 17:47:42 CST 2018 pool-1-thread-1 End. Time = Wed May 30 17:47:45 CST 2018 pool-1-thread-2 End. Time = Wed May 30 17:47:46 CST 2018 Finished all threads Process finished with exit code 0ScheduledExecutorService scheduleWithFixedDelay()方法 以初始延迟后,相对固定的频率执行任务(即无论执行某个任务多长时间,等执行完了,再延迟指定的时间执行第二个任务,它受执行任务的执行时间影响)。
for (int i = 0; i < 3; i++) { Thread.sleep(1000); WorkerThread worker = new WorkerThread("do heavy processing"); scheduledThreadPool.scheduleWithFixedDelay(worker, 0, 1, TimeUnit.SECONDS); }运行结果:
Current Time = Wed May 30 17:58:09 CST 2018 pool-1-thread-1 Start. Time = Wed May 30 17:58:10 CST 2018 pool-1-thread-2 Start. Time = Wed May 30 17:58:11 CST 2018 pool-1-thread-3 Start. Time = Wed May 30 17:58:12 CST 2018 pool-1-thread-1 End. Time = Wed May 30 17:58:15 CST 2018 pool-1-thread-2 End. Time = Wed May 30 17:58:16 CST 2018 pool-1-thread-1 Start. Time = Wed May 30 17:58:16 CST 2018 pool-1-thread-3 End. Time = Wed May 30 17:58:17 CST 2018 pool-1-thread-4 Start. Time = Wed May 30 17:58:17 CST 2018 pool-1-thread-2 Start. Time = Wed May 30 17:58:18 CST 2018 pool-1-thread-1 End. Time = Wed May 30 17:58:21 CST 2018 pool-1-thread-1 Start. Time = Wed May 30 17:58:22 CST 2018 pool-1-thread-4 End. Time = Wed May 30 17:58:22 CST 2018 pool-1-thread-2 End. Time = Wed May 30 17:58:23 CST 2018 pool-1-thread-2 Start. Time = Wed May 30 17:58:23 CST 2018 pool-1-thread-4 Start. Time = Wed May 30 17:58:24 CST 2018 pool-1-thread-1 End. Time = Wed May 30 17:58:27 CST 2018 pool-1-thread-2 End. Time = Wed May 30 17:58:28 CST 2018 pool-1-thread-1 Start. Time = Wed May 30 17:58:28 CST 2018 pool-1-thread-2 Start. Time = Wed May 30 17:58:29 CST 2018 pool-1-thread-4 End. Time = Wed May 30 17:58:29 CST 2018 pool-1-thread-4 Start. Time = Wed May 30 17:58:30 CST 2018 pool-1-thread-1 End. Time = Wed May 30 17:58:33 CST 2018 pool-1-thread-2 End. Time = Wed May 30 17:58:34 CST 2018 pool-1-thread-1 Start. Time = Wed May 30 17:58:34 CST 2018 pool-1-thread-2 Start. Time = Wed May 30 17:58:35 CST 2018 pool-1-thread-4 End. Time = Wed May 30 17:58:35 CST 2018 pool-1-thread-4 Start. Time = Wed May 30 17:58:36 CST 2018 pool-1-thread-1 End. Time = Wed May 30 17:58:39 CST 2018 pool-1-thread-2 End. Time = Wed May 30 17:58:40 CST 2018 pool-1-thread-5 Start. Time = Wed May 30 17:58:40 CST 2018 pool-1-thread-4 End. Time = Wed May 30 17:58:41 CST 2018 pool-1-thread-2 Start. Time = Wed May 30 17:58:41 CST 2018 Current Time = Wed May 30 17:58:42 CST 2018 pool-1-thread-5 End. Time = Wed May 30 17:58:45 CST 2018 pool-1-thread-2 End. Time = Wed May 30 17:58:46 CST 2018 Finished all threadsscheduleWithFixedDelay() 方法 将延迟视为两个任务开始之间的差异(即定期调用)
scheduleAtFixedRate() 方法 将延迟视为一个任务结束与下一个任务开始之间的差异
**scheduleWithFixedDelay():**创建并执行在给定的初始延迟之后,随后以给定的时间段首先启动的周期性的动作;那就是执行将在initialDelay之后开始,然后initial + period,然后是initial + 2 * period,等等。如果任务的执行遇到异常,则后续的执行被抑制。否则,任务将仅通过取消或终止执行来终止。如果任务执行时间比起周期长,则后续执行可能会迟到,但不会同时执行。
**scheduleAtFixedRate():**创建并执行在给定的初始延迟之后首先启动的周期性动作,随后在一个执行的终止和下一个执行的开始之间的延迟。如果认为的执行遇到异常,则后续的执行被抑制。否则,任务将仅通过取消或者终止来终止。
FixedThreadPool: 适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器;
SingleThreadExecutor: 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景。
CachedThreadPool: 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;
ScheduledThreadPoolExecutor: 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景,
SingleThreadScheduledExecutor: 适用于需要单个后台线程执行周期任务,同时保证顺序地执行各个任务的应用场景。
本节只是简单的介绍了一下使用线程池的好处,然后花了大量篇幅介绍Executor 框架。详细介绍了Executor 框架中ThreadPoolExecutor和ScheduledThreadPoolExecutor,并且通过实例详细讲解了ScheduledThreadPoolExecutor的使用。对于FutureTask 只是粗略带过,因为篇幅问题,并没有深究它的原理,后面的文章会进行补充。这一篇文章只是大概带大家过一下线程池的基本概览,深入讲解的地方不是很多,后续会通过源码深入研究其中比较重要的一些知识点。
《Java并发编程的艺术》
Java Scheduler ScheduledExecutorService ScheduledThreadPoolExecutor Example
java.util.concurrent.ScheduledThreadPoolExecutor Example
ThreadPoolExecutor – Java Thread Pool Example