目录
一、线程池是什么?
二、线程池参数说明
三、线程池生命周期
四、四种常见线程池
总结
一、线程池是什么?
线程池,是指管理一组工作线程的的资源池。线程池与任务队列密切相关,其中在任务队列workQueue中保存了所有等待运行的任务。 工作线程流程很简单:从任务队列获得一个任务,执行任务线程,然后返回线程池并等待下一个任务。线程池的优势有:
- 复用已存在线程,分摊请求在建立线程及销毁线程时的cpu及内存开销;
- 提高请求响应性,不会由于等待创建线程而延迟任务的执行;
- 统一管理线程池。通过适当调整线程池参数,可以创建适当数量的线程以保持cpu的使用率,同时防止相互竞争资源而导致内存耗尽。
二、线程池参数说明
通常调用Executors静态方法会创建一个线程池,其中ThreadPoolExecutor类就是实现工作线程池的主类,同时也是顶层接口Excutor的实现类。看下ThreadPoolExecutor的参数都有哪些,此方面有4个构造函数,下面选了参数最多的一个:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- int corePoolSize:线程池中核⼼线程数。核⼼线程:线程池中有两类线程,核⼼线程和⾮核⼼线程。核⼼线程默认情况下会⼀直保留在线程池中,即使这个核⼼线程是空闲的,⽽⾮核⼼线程如果⻓时间的闲置,就会被销毁(临时⼯)。在java1.6中加了一个allowCoreThreadTimeOut方法,如果此值设为true,在keepAlive时间内没有到达任务,核心线程也会被销毁。
- int maximumPoolSize:线程池中允许的最⼤线程数 。该值等于核⼼线程数量 + ⾮核⼼线程数量。
- long keepAliveTime:⾮核⼼线程闲置存活时间。如果设置了allowCoreThreadTimeOut为true,同样适用于核心线程。⾮核⼼线程如果处于闲置状态超过该值,就会被销毁。
- TimeUnit unit:keepAliveTime的单位。
- BlockingQueue workQueue:阻塞队列,维护着等待执⾏的Runnable任务。基本的队列排队方法有3种:无界队列、有界队列和同步队列。下面是几个常用的队列:
常⽤的⼏个阻塞队列:
1. LinkedBlockingQueue
无界队列,底层数据结构是链表,默认⼤⼩是 Integer.MAX_VALUE ,也可以指定⼤⼩。newFixedThreadPool和newSingleTreadExecutor默认使用该队列。如果任务快速到达,并且超过线程池的处理速度,那么队列将无限制增加,直到Integer.MAX_VALUE。
2. ArrayBlockingQueue
有界队列,底层数据结构是数组,需要指定队列的⼤⼩。有助于避免资源耗尽。
3. SynchronousQueue
同步队列,内部容量为0,每个put操作必须等待⼀个take操作,反之亦然。newCachedThreadPooll默认使用该队列。使用该队列可以避免任务排队,以便直接将任务移交给线程池处理。当线程池小于最大值时,threadPoolExecutor将创建新线程,否则根据拒绝策略拒绝任务。SynchronousQueue不是一个真正的队列,而是一种在线程间切换的机制。只有当线程池是无界的或者可以拒绝任务时,SynchronousQueue才有实际使用价值。
4. DelayedWorkQueue
延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素 。newScheduledThreadPool默认使用该队列。
- ThreadFactory threadFactory:创建线程的⼯⼚ ,⽤于创建线程。如果不指定,会新建⼀个默认的线程⼯⼚Executors.defaultThreadFactory()。DefaultThreadFactory创建的线程采用new Thread()方式,并统一设置了守护线程、线程优先级、线程名称、线程组等。
- RejectedExecutionHandler handler:拒绝处理策略,线程数量⼤于最⼤线程数就会采⽤拒绝处理策略,四种拒绝处理的策略为 :
1. ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛 出RejectedExecutionException异常。
2. ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,但是不抛出异常。
3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列头部(最旧的)的任务,然后重新尝试执⾏程序(如果再次失败,重复此过程)。
4. ThreadPoolExecutor.CallerRunsPolicy:由调⽤线程处理该任务。
三、线程池生命周期
//参考ThreadPoolExecutor.java类
// ctl:可以看作一个int类型的数字,高3位表示线程池状态,低29位表示worker数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// COUNT_BITS:Integer.SIZE=32,所以COUNT_BITS为29位
private static final int COUNT_BITS = Integer.SIZE - 3;
// CAPACITY:线程池允许的最大线程数。1左移29位,然后-1,即为2^29 -1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState存储在高3位二进制中
// 线程池5种状态,按大小排序如下:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
// runStateOf: 获取线程池状态,通过按位与操作,低29位将全部变成0
private static int runStateOf(int c) { return c & ~CAPACITY; }
// workerCountOf: 获取线程池worker数量,通过按位与操作,高3位将全部变成0
private static int workerCountOf(int c) { return c & CAPACITY; }
// ctlOf: 根据线程池状态和线程池worker数量,生成ctl值
private static int ctlOf(int rs, int wc) { return rs | wc; }
参考ThreadPoolExecutor.java文件,线程池有5种状态,如下:
- RUNNING:接受新任务并处理排队的任务。
- SHUTDOWN: 不接受新任务,但处理排队的任务,包括那些还未开始执行的任务。
- STOP :不接受新任务,不处理排队的任务,中断正在进行的任务。
- TIDYING: 所有任务都已终止,workerCount为零(ctl记录的任务数量为0),转换为TIDYING状态并将运行terminated()钩子方法。
- TERMINATED:terminated() 方法完成后变为TERMINATED状态。
线程5种状态相互转换条件:
- RUNNING -> SHUTDOWN:调用shutdown()方法
- (RUNNING or SHUTDOWN) -> STOP:调用shutdownNow() 方法,执行一个比较粗爆的关闭过程:尝试中断正在进行的任务,并且不再启动队列中尚未开始执行的任务。
- SHUTDOWN -> TIDYING:当任务队列及线程池都为空时
- STOP -> TIDYING:当线程池为空时
- TIDYING -> TERMINATED:执行terminated()钩子方法完成后
四、四种常见线程池
- newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
创建一个可缓存的线程池,适用于执行短期异步任务的场景。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,线程池的规模不受控制。)。由于不创建核心线程池,也可以在60s后,回收空闲线程,新的线程可以复用,使线程池性能可伸缩。尝试将任务添加到SynchronousQueue队列, 如果SynchronousQueue已有任务在等待,⼊列操作将会阻塞。
- newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
创建一个固定大小的线程池,因为采用无界的阻塞队列,每当提交一个任务就创建新的线程池直到最大数量,这时实际线程数量永远不会变化。核⼼线程数量和总线程数量相等,都是传⼊的参数nThreads,所以只能创建核⼼线程,不能创建⾮核⼼线程。适用于负载较重的场景,对当前线程数量进行限制,保证线程数可控。
- newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
创建一个单线程的线程池,只有一个工作线程执行任务。由于采用LinkBlockingQueue,能够保证任务依照队列的顺序来执行(例如FIFO)。
- newScheduledThreadPool
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
适用于执行延时或者周期性任务,类似于Timer。
总结
以上就是线程池的基本原理。在实际工作中除了常见的四种线程池,也可用ThreadPoolExecutor构造自定义线程池。此文仅供参考。