原标题:Java高并发之线程池详解
作者:大道方圆
https://www.cnblogs.com/xdecode
线程池优势
在业务场景中, 如果一个对象创建销毁开销比较大, 那么此时建议池化对象进行管理。例如线程, jdbc连接等等, 在高并发场景中, 如果可以复用之前销毁的对象, 那么系统效率将大大提升。另外一个好处是可以设定池化对象的上限, 例如预防创建线程数量过多导致系统崩溃的场景.
JDK中的线程池
下文主要从以下几个角度讲解:
创建线程池
我们可以通过自定义ThreadPoolExecutor或者jdk内置的Executors来创建一系列的线程池
newFixedThreadPool: 创建固定线程数量的线程池
newSingleThreadExecutor: 创建单一线程的池
newCachedThreadPool: 创建线程数量自动扩容, 自动销毁的线程池
newScheduledThreadPool: 创建支持计划任务的线程池
上述几种都是通过new ThreadPoolExecutor()来实现的, 构造函数源码如下:
1/**
2 * @paramcorePoolSize 池内核心线程数量, 超出数量的线程会进入阻塞队列
3 * @parammaximumPoolSize 最大可创建线程数量
4 * @paramkeepAliveTime 线程存活时间
5 * @paramunit 存活时间的单位
6 * @paramworkQueue 线程溢出后的阻塞队列
7 */
8publicThreadPoolExecutor(intcorePoolSize,
9intmaximumPoolSize,
10longkeepAliveTime,
11TimeUnit unit,
12BlockingQueue workQueue){
13this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
14}
15
16publicstaticExecutorService newFixedThreadPool(intnThreads){
17returnnewThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, newLinkedBlockingQueue());
18}
19
20publicstaticExecutorService newSingleThreadExecutor(){
21returnnewExecutors.FinalizableDelegatedExecutorService
22( newThreadPoolExecutor( 1, 1, 0L, TimeUnit.MILLISECONDS, newLinkedBlockingQueue()));
23}
24
25publicstaticExecutorService newCachedThreadPool(){
26returnnewThreadPoolExecutor( 0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, newSynchronousQueue());
27}
28
29publicstaticScheduledExecutorService newScheduledThreadPool(intcorePoolSize){
30returnnewScheduledThreadPoolExecutor(corePoolSize);
31}
32
33publicScheduledThreadPoolExecutor(intcorePoolSize){
34super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS, newScheduledThreadPoolExecutor.DelayedWorkQueue());
35}
提交任务
直接调用executorService.execute(runnable)或者submit(runnable)即可。
execute和submit的区别在于submit会返回Future来获取任何执行的结果.
我们看下newScheduledThreadPool的使用示例。
1publicclassSchedulePoolDemo{
2
3publicstaticvoidmain(String[] args){
4ScheduledExecutorService service = Executors.newScheduledThreadPool( 10);
5// 如果前面的任务没有完成, 调度也不会启动
6service.scheduleAtFixedRate( newRunnable() {
7@Override
8publicvoidrun(){
9try{
10Thread.sleep( 2000);
11// 每两秒打印一次.
12System. out.println(System.currentTimeMillis()/ 1000);
13} catch(InterruptedException e) {
14e.printStackTrace();
15}
16}
17}, 0, 2, TimeUnit.SECONDS);
18}
19}
潜在宕机风险
使用Executors来创建要注意潜在宕机风险.其返回的线程池对象的弊端如下:
FixedThreadPool和SingleThreadPoolPool : 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM.
CachedThreadPool和ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM.
综上所述, 在可能有大量请求的线程池场景中, 更推荐自定义ThreadPoolExecutor来创建线程池, 具体构造函数配置见下文.
线程池大小配置
一般根据任务类型进行区分, 假设CPU为N核
CPU密集型任务需要减少线程数量, 降低线程之间切换造成的开销, 可配置线程池大小为N + 1.
IO密集型任务则可以加大线程数量, 可配置线程池大小为 N * 2.
混合型任务则可以拆分为CPU密集型与IO密集型, 独立配置.
自定义阻塞队列BlockingQueue
主要存放等待执行的线程, ThreadPoolExecutor中支持自定义该队列来实现不同的排队队列.
ArrayBlockingQueue:先进先出队列,创建时指定大小, 有界;
LinkedBlockingQueue:使用链表实现的先进先出队列,默认大小为- Integer.MAX_VALUE;
SynchronousQueue:不保存提交的任务, 数据也不会缓存到队列中, 用于生产者和消费者互等对方, 一起离开.
PriorityBlockingQueue: 支持优先级的队列
回调接口
线程池提供了一些回调方法, 具体使用如下所示。
1ExecutorService service = newThreadPoolExecutor( 5, 5, 0L, TimeUnit.MILLISECONDS, newLinkedBlockingDeque()) {
2
3@Override
4protectedvoidbeforeExecute(Thread t, Runnable r){
5System. out.println( "准备执行任务: "+ r.toString());
6}
7
8@Override
9protectedvoidafterExecute(Runnable r, Throwable t){
10System. out.println( "结束任务: "+ r.toString());
11}
12
13@Override
14protectedvoidterminated(){
15System. out.println( "线程池退出");
16}
17};
可以在回调接口中, 对线程池的状态进行监控, 例如任务执行的最长时间, 平均时间, 最短时间等等, 还有一些其他的属性如下:
taskCount:线程池需要执行的任务数量.
completedTaskCount:线程池在运行过程中已完成的任务数量.小于或等于taskCount。
largestPoolSize:线程池曾经创建过的最大线程数量.通过这个数据可以知道线程池是否满过.如等于线程池的最大大小,则表示线程池曾经满了。
getPoolSize:线程池的线程数量.如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
getActiveCount:获取活动的线程数。
自定义拒绝策略
线程池满负荷运转后, 因为时间空间的问题, 可能需要拒绝掉部分任务的执行。
jdk提供了RejectedExecutionHandler接口, 并内置了几种线程拒绝策略
AbortPolicy: 直接拒绝策略, 抛出异常.
CallerRunsPolicy: 调用者自己执行任务策略.
DiscardOldestPolicy: 舍弃最老的未执行任务策略.
使用方式也很简单, 直接传参给ThreadPool
1ExecutorService service = newThreadPoolExecutor( 5, 5, 0L, TimeUnit.MILLISECONDS,
2newSynchronousQueue(),
3Executors.defaultThreadFactory(),
4newRejectedExecutionHandler() {
5@Override
6publicvoidrejectedExecution(Runnable r, ThreadPoolExecutor executor){
7System. out.println( "reject task: "+ r.toString());
8}
9});
自定义ThreadFactory
线程工厂用于创建池里的线程.。例如在工厂中都给线程setDaemon(true),,这样程序退出的时候,线程自动退出。
或者统一指定线程优先级, 设置名称等等。
1classNamedThreadFactoryimplementsThreadFactory{
2privatestaticfinalAtomicInteger threadIndex = newAtomicInteger( 0);
3privatefinalString baseName;
4privatefinalbooleandaemon;
5
6publicNamedThreadFactory(String baseName){
7this(baseName, true);
8}
9
10publicNamedThreadFactory(String baseName, booleandaemon){
11this.baseName = baseName;
12this.daemon = daemon;
13}
14
15publicThread newThread(Runnable runnable){
16Thread thread = newThread(runnable, this.baseName + "-"+ threadIndex.getAndIncrement());
17thread.setDaemon( this.daemon);
18returnthread;
19}
20}
关闭线程池
跟直接new Thread不一样, 局部变量的线程池,,需要手动关闭,,不然会导致线程泄漏问题。
默认提供两种方式关闭线程池.返回搜狐,查看更多
shutdown:等所有任务,包括阻塞队列中的执行完,才会终止,但是不会接受新任务。
shutdownNow:立即终止线程池,,打断正在执行的任务, 清空队列。
责任编辑: