线程和线程池
- 为啥使用线程池 线程池的优势
(1)、降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)、提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)、方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)、提供更强大的功能,例如:延时定时线程池。
- JDK提供了几种线程池类型
阿里开发手册中说明,不建议使用这几种现成的线程池;
newCachedThreadPool 在高并发环境中,CPU可能搞爆;
newSingleThreadExecutor 和newFixedThreadPool在高并发的时候可能会内存爆;
- 定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 只有一条线程来执行任务,适用于有顺序的任务的应用场景
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- 可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)
2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
4、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。
5、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
5、handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。
有默认四种:
第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
第二种DisCardPolicy:不执行新任务,也不抛出异常
第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
第四种CallerRunsPolicy:直接调用execute来执行当前任务
- ThreadPoolExecutor中的几个属性解释
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 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; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
AtomicInteger类型的ctl代表了ThreadPoolExecutor中的控制状态,它是一个复核类型的成员变量,是一个原子整数,借助高低位包装了两个概念:
(1)workerCount:线程池中当前活动的线程数量,占据ctl的低29位;线程池中当前活动的线程数量,它占据ctl的低29位,这样,每当活跃线程数增加或减少时,ctl直接做相应数目的增减即可,十分方便。而ThreadPoolExecutor中COUNT_BITS就代表了workerCount所占位数,定义如下:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
(2)runState:线程池运行状态,占据ctl的高3位,有RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED五种状态。
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
在Java中,一个int占据32位,而COUNT_BITS的结果不言而喻,Integer大小32减去3,就是29;另外,既然workerCount代表了线程池中当前活动的线程数量,那么
它肯定有个上下限阈值,下限很明显就是0,上限呢?ThreadPoolExecutor中CAPACITY就代表了workerCount的上限,它是ThreadPoolExecutor中理论上的最大活跃线程数,其定义如下:
运算过程为1左移29位,也就是00000000 00000000 00000000 00000001 --> 001 0000 00000000 00000000 00000000,再减去1的话,就是 000 11111 11111111 11111111 11111111,前三位代表线程池运行状态runState,所以这里workerCount的理论最大值就应该是29个1,即536870911;
既然workerCount作为其中一个概念复合在AtomicInteger ctl中,那么ThreadPoolExecutor理应提供从AtomicInteger ctl中解析出workerCount的方法,如下:
private static int workerCountOf(int c) { return c & CAPACITY; }
计算逻辑很简单,传入的c代表的是ctl的值,即高3位为线程池运行状态runState,低29位为线程池中当前活动的线程数量workerCount,将其与CAPACITY进行与操作&,也就是与000 11111 11111111 11111111 11111111进行与操作,c的前三位通过与000进行与操作,无论c前三位为何值,最终都会变成000,也就是舍弃前三位的值,而c的低29位与29个1进行与操作,c的低29位还是会保持原值,这样就从AtomicInteger ctl中解析出了workerCount的值。
接下来,我们再看下runState:线程池运行状态,它占据ctl的高3位,有RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED五种状态。我们先分别解释下这五种状态:
(1)RUNNING:接受新任务,并处理队列任务
private static final int RUNNING = -1 << COUNT_BITS;
-1在Java底层是由32个1表示的,左移29位的话,即111 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位全部为1的话,表示RUNNING状态,即-536870912;
(2)SHUTDOWN:不接受新任务,但会处理队列任务
private static final int SHUTDOWN = 0 << COUNT_BITS;
0在Java底层是由32个0表示的,无论左移多少位,还是32个0,即000 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位全部为0的话,表示SHUTDOWN状态,即0;
(3)STOP:不接受新任务,不会处理队列任务,而且会中断正在处理过程中的任务
private static final int STOP = 1 << COUNT_BITS;
1在Java底层是由前面的31个0和1个1组成的,左移29位的话,即001 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为001的话,表示STOP状态,即536870912;
(4)TIDYING:所有的任务已结束,workerCount为0,线程过渡到TIDYING状态,将会执行terminated()钩子方法
private static final int TIDYING = 2 << COUNT_BITS;
2在Java底层是由前面的30个0和1个10组成的,左移29位的话,即010 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为010的话,表示TIDYING状态,即1073741824;
(5)TERMINATED:terminated()方法已经完成
private static final int TERMINATED = 3 << COUNT_BITS;
在Java底层是由前面的30个0和1个11组成的,左移29位的话,即011 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为011的话,表示TERMINATED状态,即1610612736;
由上面我们可以得知,运行状态的值按照RUNNING–>SHUTDOWN–>STOP–>TIDYING–>TERMINATED顺序值是递增的,这些值之间的数值顺序很重要。随着时间的推移,运行状态单调增加,但是不需要经过每个状态。那么,可能存在的线程池状态的转换是什么呢?如下:
(1)RUNNING -> SHUTDOWN:调用shutdownNow()方法后,或者线程池实现了finalize方法,在里面调用了shutdown方法,即隐式调用;
(2)(RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()方法后;
(3)SHUTDOWN -> TIDYING:线程池和队列均为空时;
(4)STOP -> TIDYING:线程池为空时;
(5)TIDYING -> TERMINATED:terminated()钩子方法完成时。
我们再来看下是实现获取运行状态的runStateOf()方法,代码如下:
private static int runStateOf(int c) { return c & ~CAPACITY; }
~是按位取反的意思,CAPACITY表示的是高位的3个0,和低位的29个1,而~CAPACITY则表示高位的3个1,2低位的9个0,然后再与入参c执行按位与操作,即高3位保持原样,低29位全部设置为0,也就获取了线程池的运行状态runState。
最后,我们再看下原子变量ctl的初始化方法ctlOf(),代码如下:
private static int ctlOf(int rs, int wc) { return rs | wc; }