参考文章
为什么使用线程池
- javadoc中的原文
Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and managing the resources,including threads, consumed when executing a collection of tasks.Each {ThreadPoolExecutor} also maintains some basic statistics, such as the number of completed tasks.
- 翻译
- 线程池解决了两个问题
- 其一:执行大量异步任务的时候,依靠减少每个任务的调用开销来提高性能。在执行任务期间提供了一个方式来管理系统资源,维护资源边界。如线程资源。
- 其二:统计,可以维护一些比如任务已完成量的统计
- 验证
- 线程池高效验证
public class ThreadSwitchTest {
//C
public volatile static int num = 0;
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,10,5000, TimeUnit.SECONDS,
new LinkedBlockingDeque<>());
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
//B
// new Thread(new IncreaseTask()).start();
//A
// threadPoolExecutor.execute(new IncreaseTask());
}
while (ThreadSwitchTest.num < 900) {
//E
// System.out.println(threadPoolExecutor.getCompletedTaskCount());
}
threadPoolExecutor.shutdown();
System.out.println(System.currentTimeMillis() - begin);
}
}
class IncreaseTask implements Runnable {
@Override
public void run() {
// D处注释
// try {
// Thread.sleep(3000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
ThreadSwitchTest.num++;
}
}
任务数 | A | B |
---|---|---|
1000 | 10ms | 156ms |
100 | 7ms | 15ms |
上述代码显然证明了线程池因为创建线程消耗的资源远小于直接创建目标线程数量所消耗的资源,而这里有一个彩蛋便是C处,如果把volatile关键字去掉你的机器上的运行结果是什么呢?
- 资源边界
使用JDK中的jvisualvm.exe程序高峰时期线程数,以及总线程数
类目 | A | B |
---|---|---|
总线程数 | 10 | 1013 |
峰值线程数 | 10 | 83 |
线程池通过控制同一时间执行任务数量来做到控制资源的使用。如上述线程池最大线程池数量为10.则任意时刻改程序使用的内存、CPU计算量都不会超过10个线程的使用资源。
这里如果建D处注释放开,则B处执行将远比A处执行快速结束,也再一次说明线程池就是通过控制并发,将原来的大量异步任务控制资源的形式使任务在一定程度上串行执行。
睡眠时间 | A | B |
---|---|---|
3ms | 284 | 110 |
30ms | 2741 | 121 |
这里可以举一个小例子,100个古人要赶集过河需要坐船,非线程池方式是自己制造船,然后划过对岸,到达对岸就销毁船只。而非线程池方式是管理者创建固定10条船与船夫。船夫将排队的人渡到对岸,再回到出发点继续渡人。这样的好处是防止河道阻塞(资源控制),千帆竞发,船只重复利用。当然这里也有坏处,就是会降低一部分效率。甚至如果乘客坏一点儿一直不下船(死循环)会导致后面旅客无法出发
- 任务统计
任务统计指的是能够计算当前任务执行的数量。如果代码E处放开,可以查询线程池当前执行任务数量
线程池控制变量
- 原文
The main pool control state, ctl, is an atomic integer packing two conceptual fields,workerCount, indicating the effective number of threads runState,indicating whether running, shutting down etc
- 翻译
线程池中有个控制变量ctl。自增integer,包含了两个概念,当前的工作线程数量,当前线程池中的状态 - 线程池中ctl与状态量的关系
- COUNT_BTS = 29
- CAPACITY = (1 << COUNT_BITS)-1 = 1 * 2^29 -1 = 000111…111;前三位为0,后二十九位为1
- RUNNING = -1<<COUNT_BITS =-1 * 2^29 = 111000…000,前三个为1后二十九个为0
可以接收新任务 - SHUTDOWN = 0<<COUNT_BITS = 0* 2^29 = 000 000…000,前三个为0后二十九个为0
停止接受新任务,处理任务直到队列为空 - STOP = 1 << COUNT_BITS = 1 *2^29 = 001 000…000
停止接受新任务,丢弃队列任务,终止当前执行任务 - TIDING = 2 << COUNT_BITS = 2^30 = 010 000…000
整理状态,队列任务为空,线程池中活动线程数量为0 - TERMINATE = 3 << COUNT_BITS = 3 * 2^29 = 011 000…000
退出状态,terminated方法执行完毕即推出当前系统状态
5 通过ctl计算运行状态和员工数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 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; }
- runStateOf:计算当前系统运行状态,因为系统状态在ctl的高位。所以应该取&,并且低位应该为0.所以state&~capacity
- workerCountof:计算当前线程池中线程数量,线程池数量在ctl的低位。所以应该取&,并且高位应该为0,所以state&capacity
- ctlof:将运行状态参数与线程池中活动线程数量进行整合获得ctl参数。运行状态一定在高位。活动线程数量在低位。并且为高低位的整合。所以应该为‘|’或变量
- 思考
- 为什么线程池中活动线程数量与当前线程池状态这两个变量要放在一个int型的数值里面?
因为线程池是并发运行。思考一种情况:A线程尝试调用shutdown()修改线程池状态,B线程池尝试submit(task)。如果用两个int值。B线程的在workerCount+1的时候必须保证A线程未修改线程池状态为shutdown,则B线程CAS将(wc,wc+1)操作时候必须同时保证shutdown不变性。换言之如果用CAS则应该为cas(state,state) and cas(wc,wc+1)。但是如果用一个变量来表达则简单了很多。利用CAS就能够保证原子性。