线程池是并发包里面很重要的一部分,在实际情况中也是使用很多的一个重要组件。
下图描述的是线程池API的一部分。广义上的完整线程池可能还包括Thread/Runnable、Timer/TimerTask等部分。这里只介绍主要的和高级的API以及架构和原理。
任务的执行策略包括4W3H部分:
在后面的章节中会详细分写这些策略是如何实现的。我们先来简单回答些如何满足上面的条件。
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
下面这张图完整描述了线程池的类体系结构。
首先Executor的execute方法只是执行一个Runnable的任务,当然了从某种角度上将最后的实现类也是在线程中启动此任务的。根据线程池的执行策略最后这个任务可能在新的线程中执行,或者线程池中的某个线程,甚至是调用者线程中执行(相当于直接运行Runnable的run方法)。这点在后面会详细说明。
ExecutorService在Executor的基础上增加了一些方法,其中有两个核心的方法:
这两个方法都是向线程池中提交任务,它们的区别在于Runnable在执行完毕后没有结果,Callable执行完毕后有一个结果。这在多个线程中传递状态和结果是非常有用的。另外他们的相同点在于都返回一个Future对象。Future对象可以阻塞线程直到运行完毕(获取结果,如果有的话),也可以取消任务执行,当然也能够检测任务是否被取消或者是否执行完毕。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在
Executors类里面提供了一些静态工厂,生成一些常用的线程池。
由于已经看到了ThreadPoolExecutor的源码,因此很容易就看到了ThreadPoolExecutor线程池的数据结构。下图3描述了这种数据结构。
图3 ThreadPoolExecutor 数据结构
其实,即使没有上述图形描述ThreadPoolExecutor的数据结构,我们根据线程池的要求也很能够猜测出其数据结构出来。
线程池Executor是异步的执行任务,因此任何时刻不能够直接获取提交的任务的状态。这些任务有可能已经完成,也有可能正在执行或者还在排队等待执行。因此关闭线程池可能出现一下几种情况:
另外关闭线程池后对于任务的状态应该有相应的反馈信息。
图4 描述了线程池的4种状态。
图4
线程池的API如下:
图5
其中shutdownNow()会返回那些已经进入了队列但是还没有执行的任务列表。awaitTermination描述的是等待线程池关闭的时间,如果等待时间线程池还没有关闭将会抛出一个超时异常。
对于关闭线程池期间发生的任务提交情况就会触发一个拒绝执行的操作。这是java.util.concurrent.RejectedExecutionHandler描述的任务操作。下一个小结中将描述这些任务被拒绝后的操作。
总结下这个小节:
紧接上面,对于关闭线程池期间发生的任务提交情况就会触发一个拒绝执行的操作。这是java.util.concurrent.RejectedExecutionHandler描述的任务操作。
先来分析下为什么有任务拒绝的情况发生。
这里先假设一个前提:线程池有一个任务队列,用于缓存所有待处理的任务,正在处理的任务将从任务队列中移除。因此在任务队列长度有限的情况下就会出现新任务的拒绝处理问题,需要有一种策略来处理应该加入任务队列却因为队列已满无法加入的情况。另外在线程池关闭的时候也需要对任务加入队列操作进行额外的协调处理。
RejectedExecutionHandler提供了四种方式来处理任务拒绝策略。
这四种策略是独立无关的,是对任务拒绝处理的四种表现形式。
最简单的方式就是直接丢弃任务。但是却有两种方式,到底是该丢弃哪一个任务,比如可以丢弃当前将要加入队列的任务本身(DiscardPolicy)或者丢弃任务队列中最旧任务(DiscardOldestPolicy)。丢弃最旧任务也不是简单的丢弃最旧的任务,而是有一些额外的处理。除了丢弃任务还可以直接抛出一个异常(RejectedExecutionException),这是比较简单的方式。抛出异常的方式(AbortPolicy)尽管实现方式比较简单,但是由于抛出一个RuntimeException,因此会中断调用者的处理过程。除了抛出异常以外还可以不进入线程池执行,在这种方式(CallerRunsPolicy)中任务将有调用者线程去执行。
上面是一些理论知识,下面结合一些例子进行分析讨论。
package xylz.study.concurrency; import java.lang.reflect.Field; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy; import java.util.concurrent.ThreadPoolExecutor.DiscardPolicy; public class ExecutorServiceDemo { static void log(String msg) { System.out.println(System.currentTimeMillis() + " -> " + msg); } static int getThreadPoolRunState(ThreadPoolExecutor pool) throws Exception { Field f = ThreadPoolExecutor.class.getDeclaredField("runState"); f.setAccessible(true); int v = f.getInt(pool); return v; } public static void main(String[] args) throws Exception { ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1)); pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); for (int i = 0; i < 10; i++) { final int index = i; pool.submit(new Runnable() { public void run() { log("run task:" + index + " -> " + Thread.currentThread().getName()); try { Thread.sleep(1000L); } catch (Exception e) { e.printStackTrace(); } log("run over:" + index + " -> " + Thread.currentThread().getName()); } }); } log("before sleep"); Thread.sleep(4000L); log("before shutdown()"); pool.shutdown(); log("after shutdown(),pool.isTerminated=" + pool.isTerminated()); pool.awaitTermination(1000L, TimeUnit.SECONDS); log("now,pool.isTerminated=" + pool.isTerminated() + ", state=" + getThreadPoolRunState(pool)); } }
第一种方式直接丢弃(DiscardPolicy)的输出结果是:
1294494050696 -> run task:0 1294494050696 -> before sleep 1294494051697 -> run over:0 -> pool-1-thread-1 1294494051697 -> run task:1 1294494052697 -> run over:1 -> pool-1-thread-1 1294494054697 -> before shutdown() 1294494054697 -> after shutdown(),pool.isTerminated=false 1294494054698 -> now,pool.isTerminated=true, state=3
对于上面的结果需要补充几点。
如果把策略换成丢弃最旧任务(DiscardOldestPolicy),结果会稍有不同。
1294494484622 -> run task:0 1294494484622 -> before sleep 1294494485622 -> run over:0 -> pool-1-thread-1 1294494485622 -> run task:9 1294494486622 -> run over:9 -> pool-1-thread-1 1294494488622 -> before shutdown() 1294494488622 -> after shutdown(),pool.isTerminated=false 1294494488623 -> now,pool.isTerminated=true, state=3
这里依然只是执行两个任务,但是换成了任务task0和task9。实际上task1~task8还是进入了任务队列,只不过被task9挤出去了。
对于异常策略(AbortPolicy)就比较简单,这回调用线程的任务执行。
对于调用线程执行方式(CallerRunsPolicy),输出的结果就有意思了。
1294496076266 -> run task:2 -> main 1294496076266 -> run task:0 -> pool-1-thread-1 1294496077266 -> run over:0 -> pool-1-thread-1 1294496077266 -> run task:1 -> pool-1-thread-1 1294496077266 -> run over:2 -> main 1294496077266 -> run task:4 -> main 1294496078267 -> run over:4 -> main 1294496078267 -> run task:5 -> main 1294496078267 -> run over:1 -> pool-1-thread-1 1294496078267 -> run task:3 -> pool-1-thread-1 1294496079267 -> run over:3 -> pool-1-thread-1 1294496079267 -> run over:5 -> main 1294496079267 -> run task:7 -> main 1294496079267 -> run task:6 -> pool-1-thread-1 1294496080267 -> run over:7 -> main 1294496080267 -> run task:9 -> main 1294496080267 -> run over:6 -> pool-1-thread-1 1294496080267 -> run task:8 -> pool-1-thread-1 1294496081268 -> run over:9 -> main 1294496081268 -> before sleep 1294496081268 -> run over:8 -> pool-1-thread-1 1294496085268 -> before shutdown() 1294496085268 -> after shutdown(),pool.isTerminated=false 1294496085269 -> now,pool.isTerminated=true, state=3
参考内容:
深入浅出 Java Concurrency (28): 线程池 part 1 简介
http://www.blogjava.net/xylz/archive/2010/12/19/341098.html
深入浅出 Java Concurrency (29): 线程池 part 2 Executor 以及Executors
http://www.blogjava.net/xylz/archive/2010/12/21/341281.html
深入浅出 Java Concurrency (30): 线程池 part 3 Executor 生命周期
http://www.blogjava.net/xylz/archive/2011/01/04/342316.html
深入浅出 Java Concurrency (31): 线程池 part 4 线程池任务拒绝策略
http://www.blogjava.net/xylz/archive/2011/01/08/342609.html
java的concurrent用法详解
http://blog.csdn.net/a511596982/article/details/8063742