一. 线程池的好处
- 降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等待线程的创建就能直接执行。
- 增加线程的可管理性。线程是稀缺资源,使用线程池进行同一个的分配,调优和监控。
二. 线程池的七个参数
-
corePoolSize(线程池的基本大小):线程池维护的一个最小线程数量。一旦创建出来,只要不大于corePoolSize数量,就算线程处于空闲也不会被销毁。这个参数就是线程池的最小线程数量。
-
maximumPoolSize(线程池最大数量):一个任务被提交到线程池后,首先会找到空闲存活的线程,如果有,则直接将任务交给空闲线程来执行;如果没有,则会缓存到工作队列中,如果工作队列满了,才会创建一个新的线程,新创建的线程会直接执行新加入的任务,然后再去找工作队列的头部任务,但是线程池不会无限的创建线程,它会有一个最大的线程数量,这个参数就是决定线程池创建的线程最大数量。
-
keepAliveTime(空闲线程存活时间):如果线程处于空闲状态,且当前线程数量大于corePoolSize的最小线程数量,那么过了这个参数指定的时间后,这些空闲的线程就会被销毁。
-
unit(空闲线程存活的单位):就是keepAliveTime的时间单位。
-
workQueue(工作队列):任务被提交后,如果线程数量大于corePoolSize,则会先进入工作队列中,任务调度时再从队列中取出,jdk提供四种工作队列:
- ArrayBlockingQueue:基础数组的有界阻塞队列,按FIFO排序。当任务被提交后,如果线程数量大于等于corePoolSize,则会将任务放入队尾,等待被调度。如果队列满了,如果线程数量少于maximumPoolSize则会创建新的线程,拿取队头的任务执行;如果队列满了,且线程数量到达maximumPoolSize,则会执行拒绝策略。
- LinkBlockingQueue:基于链表的无界阻塞队列(默认最大容量是Interger.MAX),按FIFO排序,由于队列近似无界,所以当线程池中的线程数量大于corePoolSize后,再有新任务进来时,会一直加入工作队列,而不会创建新的线程,所以该工作队列,maximumPoolSize没有作用。
- SynchronousQueue:一个不缓存任务的阻塞队列,生产者放入任务就必须等待消费者取出任务。如果没有可用的线程,则会创建新地新城,如果线程数量达到maxPoolSize,则会执行拒绝策略。
- PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
-
threadFactory(线程工厂):创建一个新线程时用的工厂,可以用来设定线程名。
-
handler(拒绝策略):当工作队列的任务达到最大,并且线程池无法再创建新的线程取出任务的时候,则会执行拒绝策略,策略默认是AbortPolicy。jdk提供4种拒绝策略
- CallerRunsPolicy:在该策略下,调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
- AbortPolicy:在该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
- DiscardPolicy:在该策略下,直接丢弃任务,什么都不做。
- DiscardOldestPolicy:在该策略下,抛弃进入队列最早的那个任务,也就是第二个任务,然后尝试把这次拒绝的任务放入队列。
- CallerRunsPolicy:在该策略下,调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
三. Executors 4种创建线程池的方法
- newFixedThreadPool:固定线程数量的线程池。corePoolSize = maximumPoolSize, keepAliveTime为0,工作队列使用无界的LinkedBlockingQueue。使用于为了满足资源管理的需求,而需要限制当前线程数量的场景,适用于负载比较重的服务器。
- newSingleThreadExecutor:只有一个线程的线程池。corePoolSize = maximumPoolSize = 1,keepAliveTime为0,工作队列使用无界的LinkedBlockingQueue。适用于需要保证循序的执行各个任务的场景。
- newCachedThreadPool:按需要创建新线程的线程池。核心线程数为0,最大线程数为Integer.MAX_VALUE,keepAliveTime为60秒,工作队列使用同步移交SynchronousQueue。该线程池可以无限扩展,当需求增加时,可以添加新的线程,而当需求降低时会自动回收空闲线程。适用于执行很多的短期异步任务,或者是负载较轻的服务器。
- newScheduledThreadPool:创建一个以延迟或定时的方式啦执行任务的线程池,工作队列是为DelayedWorkQueue,适用于需要多个后台线程执行周期任务。
三. 线程池的5种状态
- RUNNING:接受任务并处理排队的任务。
- SHUTDOWN:不接受新任务,但处理排队的任务。
- STOP:不接受新任务,不处理排队的任务,并中断正在进行的任务。
- TIDYING:所有任务都已终止,workerCount为零,线程转换到TIDYING状态将运行terminated()钩子方法。
- TERMINATED:terminated()已完成。
1. 状态之间的转换
2. 线程池ctl属性的底层含义
既然说到了线程池的状态,必然少不了其底层状态和线程数量是如何存储的。
ctl是一个打包两个概念字段的原子整数。
- workerCount:指示线程的有效数量;
- runState:指示线程池的运行状态,有RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED状态。
int有32位,其中ctl的低29位用于表示workerCount,高3位表示runState。
下面我们看看源码:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; // 29
private static final int CAPACITY = (1 << COUNT_BITS) - 1;// 代码块1
// runState is stored in the high-order bits
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
private static int runStateOf(int c) { return c & ~CAPACITY; }//代码块2
private static int workerCountOf(int c) { return c & CAPACITY; }//代码块3
private static int ctlOf(int rs, int wc) { return rs | wc; }//代码块4
为什么状态占3位呢?
因为5种状态,5个常数分别左移29位,符号位占一位,2,3占两位,要想完整的表示5种状态,就得用3位。
代码块1表示:1左移29位后是 ,10 0000 0000 0000 0000 0000 0000 0000 ,-1 等于29个1,
代码块2表示:由代码1可知,CAPACITY等于29个1,按位取反后,低29位全为0,高3位为1,然后跟ctl与运算,可以得到runState的信息。也就是高3位。
代码块3表示:根据代码1知道,低29位全为1,则与运算得到workerCount的值。
代码块4表示:将workerCount或上runState得到stl。