深入浅出Java线程池:使用篇 (3)

CPU密集型任务指的是需要cpu进行大量计算的任务,这个时候我们需要尽可能地压榨CPU的利用率。此时核心数不宜设置过大,太多的线程会互相抢占cpu资源导致不断切换线程,反而浪费了cpu。最理想的情况是每个CPU都在进行计算,没有浪费。但很有可能其中的一个线程会突然挂起等待IO,此时额外的一个等待线程就可以马上进行工作,而不必等待挂起结束。

IO密集型任务指的是任务需要频繁进行IO操作,这些操作会导致线程长时间处于挂起状态,那么需要更多的线程来进行工作,不会让cpu都处于挂起状态,浪费资源。一般设置为cpu核心数的两倍即可。

当然实际情况还需要根据任务具体特征来配置,例如系统资源有限,有一些线程被挂在后台持续工作,那么这个时候就必须适当减少线程数,从而减少线层切换的次数。

第二是阻塞队列的配置。

阻塞队列有4个内置的选择:LinkedBlockingQueue、ArrayBlockingQueue、SychronizeQueue和PriorityBlockingQueue,其次还有比较少用的DelayedWorkQueue。

如果学习过Android的Handler机制的话,会发现他们跟Handler内部的MessageQueue是非常像的。ThreadPoolExecutor中的线程,相当于Handler的Looper;阻塞队列,相当于Handler的MessageQueue。他们都会去队列中获取新的任务,当队列为空时,queue.poll() 方法会进行阻塞,直到下一个新的任务来临。

LinkedBlockingQueue、ArrayBlockingQueue都是普通的阻塞队列,尾插头出;区别是后者在创建的时候必须指定长度。
SychronizeQueue是一个没有容量的队列,每一个插入到这个队列的任务必须马上找到可以执行的线程,如果没有则拒绝执行。
PriorityBlockingQueue是具有优先级的阻塞队列,里面的任务会根据设置的优先级进行排序。所以优先级低的任务可能会一直被优先级高的任务顶下去而得不到执行。
DelayedWorkQueue则非常像Handler中的MessageQueue了,可以给任务设置延时。

阻塞队列的选择也是非常重要,一般来说必须要指定阻塞队列的长度。如果使用无限长的队列,那么有可能在大量的任务到来时直接OOM,程序崩溃。

第三是线程总数。

在阻塞队列满了之后,如果线程总数还没到达上限,会创建额外的线程来执行。这对应付突然的大量但轻量的任务的时候有奇效。通过创建更多的线程来提高并发效率。

但是同时也要注意,如果线程数量太多,会造成频繁进行线程切换导致高昂的系统开销。

第四是线程存活时间。

这里一般指非核心线程的存活时间。这个参数的意义在于,当线程都进入空闲的时候,可以回收部分线程来减少系统资源占用。为了提高线程池的响应速度,一般比较少去回收核心线程。

第五是线程工厂。

这个参数比较简单,继承接口然后重写 newThread 方法即可。主要的用途在于给创建的线程设置一个有意义的名字。

最后一个是拒绝策略者。

ThreadPoolExecutor内置有4种策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。默认是使用AbortPolicy,我们可以在构造方法中传入这些类的实例,在任务被拒绝的时候,会回调他们的 rejectedExecution 方法。

AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

DiscardPolicy:也是丢弃任务,但是不抛出异常。

DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

CallerRunsPolicy:由调用线程直接执行该任务

当然我们也可以自己实现RejectedExecutionHandler接口来自定义自己的拒绝策略。

线程池的使用

execute(Runnable command) :执行一个任务

Future submit(Runnable/Callable):提交一个任务,这个任务拥有返回值

shutDown/shutDownNow:关闭线程池。后者还有去尝试中断正在执行的任务,也就是调用正在执行的线程的interrupt方法

preStartAllCoreThread:提前启动所有的线程

isShutDown:线程池是否被关闭

isTerminad:线程池是否被终止。区别于shutDown,这个状态下的线程池没有正在执行的任务

这些都是比较常用的api,更多的api读者可以去阅读api文档。

监控线程池

ThreadPoolExecutor中有很多参数可以提供我们参考线程池的运行情况:

largestPoolSize:线程池中曾经到达的最大线程数量

completedTaskCount:完成的任务个数

getAliveCount:获取当前正在执行任务的线程数

getTaskCount:获取当前任务个数

getPoolSize:获取当前线程数

此外还有很多的get方法来获取相关参数,提供动态的线程池运行情况监控,感兴趣的读者可以去阅读相关的api。

ThreadPoolExecutor中还有提供了一些的回调方法:

beforeExecute:在任务执行前被调用

afterExecute:在任务执行之后被调用

terminated:在线程池终止之后被调用

我们可以通过继承ThreadPoolExecutor来重写这些方法。

ScheduledThreadPoolExecutor 概述

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpszdy.html