常用的三种线程池
三种常用线程池的创建方法如下:
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
public class ThreadPool {
public static void main(String[] args){
//固定线程数目线程池 newFixedThreadPool
ExecutorService es0 = Executors.newFixedThreadPool(5);
//可缓存线程池 newCachedThreadPool
ExecutorService es1 = Executors.newCachedThreadPool();
//单线程线程池 newSingleThreadExecutor
ExecutorService es2 = Executors.newSingleThreadExecutor();
}
}
点开每个线程池,看看他们的源码,可以看出他们都有一个相同的调用ThreadPoolExecutor
:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, //<- 这一行
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, //<- 这一行
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new Executors.FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, //<- 这一行
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
我们再查看ThreadPoolExecutor
的源码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
发现他有七个参数
线程池的七大参数
-
corePoolSize
:线程池中的常驻核心线程数 -
maximumPoolSize
:线程池中能够容纳同时执行的最大线程数,此值必须大于等于1 -
keepAliveTime
:多余的空闲线程池的存活时间,当前池中线程数超过 corePoolSize 时,当空闲时间达到 keepAliveTime 时,多余线程会被销毁直到只剩下 corePoolSize 个线程为止 -
unit
:keepAliveTime 的单位 -
workQueue
:任务队列,被提交但尚未被执行的任务 -
threadFactory
:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认即可 -
handler
:拒绝策略,表示队列满时,并且工作线程大于等于 maximumPoolSize 时,如何拒绝请求执行的 runnable 策略
七大参数之间的关系(线程池运行流程)
线程池启动,创建corePoolSize
个数量的核心线程,这些线程不管有无任务都会存在,直到线程池被关闭;当任务不断送入线程池,核心线程全部被占用,再有任务被送进来时,存入workQueue
任务队列,当有核心线程完成任务时,从任务队列取任务执行;当任务队列满时,线程池会创建一般线程,核心线程数+一般线程数 <= maximumPoolSize
,新创建的线程会从任务队列中取任务执行;当线程池中核心线程与一般线程都被占用,且任务队列也满时,线程池会启动handler
拒绝策略,阻止线程送入线程池;当任务队列中的任务慢慢被线程取走,任务队列为空,且存在线程为空闲状态,那么这些空闲的线程会等待keepAliveTime
的存活时间,如果在存活时间内一直处于空闲状态,那么这个线程会被销毁,知道线程池中的线程数目达到 corePoolSize 。
注:上述的 核心线程 与 一般线程 是为了更好的区分 corePoolSize 与 maximumPoolSize ;线程池不会标记哪个线程是核心线程,哪个线程是一般线程,所有的线程都是相同的。
线程池的四大拒绝策略
AbortPolicy()
:抛出一个异常,默认的DiscardPolicy()
:直接丢弃任务DiscardOldestPolicy()
:丢弃队列里最老的任务,将当前这个任务继续提交给线程池CallerRunsPolicy()
:交给线程池调用所在的线程进行处理,即谁提交的任务返回给谁
如何选择线程池
- newFixedThreadPool 与 newSingleThreadExecutor:
允许的任务队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,导致OOM
OOM:OutOfMemory,内存溢出,程序申请内存过大,JVM无法满足,导致程序崩溃
- newSingleThreadExecutor 与 s.newScheduledThreadPo:
允许创建的线程数目为Integer.MAX_VALUE,可能会创建大量的线程,提高CPU的占用率,导致OOM
阿里巴巴Java开发手册强制要求不允许使用Executors去创建线程池,而是通过ThreadPoolExecutor自行创建线程池。
ExecutorService es = new ThreadPoolExecutor(
5, //corePoolSize
20, //maximumPoolSize
5L, //keepAliveSize
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
如上代码是一个自定义的线程池,他的 corePoolSize 等于5; maximumPoolSize 等于 20; keepAliveSize 为5秒;使用长度为10的 LinkedBlockingQueue 阻塞队列;使用默认的线程工厂与 AbortPolicy 拒绝策略。
如何为线程池配置合理的线程数
根据任务的种类分为CPU密集型与IO密集型
CPU密集型
CPU密集型指该任务需要大量的计算,而没有阻塞,CPU一直全速运行;CPU密集型应该尽可能的减少线程数量,以减少线程切换带来的性能损耗,一般配置 CPU核数+1 个线程的线程池
IO密集型
IO密集型指该任务需要大量的IO,即大量的阻塞,使用多线程可以利用被浪费掉的阻塞时间,因此需要多配置线程数,一般配置 CPU核数 /(1 - 阻塞系数) 个线程的线程池,阻塞系数一般为0.8~0.9
以上为个人学习总结,如果疏漏、错误之处,欢迎指出。