去面试的同学,对线程池的那些参数,大都念念有词。核心线程,阻塞队列,最大线程数,保活时间等。而这些只是冰山一角,背后运行的机制更加精妙有趣,这要从一个朴素的入门例子说起。
朴素的线程入门例子
Thread, Runnable,Callable,线程池等。(很长一段时间,我把两个able等同于线程,其实并不是,他们依赖于Thread启动运行,只能算是一个可以执行的任务。在线程池中可以领悟到这个差异)
public class MyTask implements Callable<String>{
在main
()方法中
MyTask myTask = new MyTask();
FutureTask<String> futureTask = new FutureTask<String>(myTask);
Thread worker=new Thread(futureTask);
worker.start();
System.out.println(futureTask.get());
使用过线程池很久之后,依然深深的迷惑,半知不解,却无从入手。
1,入门例子主/子线程执行完就结束了,线程池里面的线程怎么做到不结束呢
2,线程池空闲的时候,存活线程在干啥呢
3,保活时间过了,怎么释放线程呢
4,代码中总看到死循环(或称为自旋),这个不会浪费cpu吗,一次次等待时间片用完?
5,线程池是怎么调度的,每次有任务时,是一窝蜂抢,还是明确指定某个线程领某个任务呢
听我分解:
1,首先有一个线程池,岁月还很静好
//复用上面的类 MyTask myTask = new MyTask(); ExecutorService service = Executors.newCachedThreadPool(); Future<String> future = service.submit(myTask); //异步 System.out.println(future.get());//阻塞 service.shutdown();
2,生产者有一搭没一搭的,往里边扔任务(就是你写的业务代码),扔的任务被下面接住了
//AbstractExecutorService.submit
public <T> Future<T> submit(Callable<T> task);
//扔一个任务,我封装一下,为了返回结果方便(前文有讲)
RunnableFuture<T> ftask = newTaskFor(task);
//重要!!!留的模板方法,让子类实现
execute(ftask);
return ftask;
3,每当来任务时,线程池一番判断(execute方法)
,要不创建线程,要不扔进阻塞队列,要不扔掉任务(没错,你念念有词的那段)
4,为了更形象,线程池请来了 worker 概念,下面 newThread 真像黄袍加身,自己坐上去了。
//ThreadPoolExecutor 的私有内部类 Worker private final class Worker extends AbstractQueuedSynchronizer implements Runnable final Thread thread; Runnable firstTask; volatile long completedTasks; Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); }
注意看,因为 worker 继承了 runnable,start 的时候,调用的是他的 run 方法,而不是你扔进去的任务的 run。
//这是worker的run方法
public void run() {
runWorker(this);
}
runworker 里面才想起取 task 来执行,并且是直接执行 run(当做普通方法调用),而不是 start(启动新线程才这么干)。
//如下是简化后的方法 ThreadPoolExecutor final void runWorker(Worker w) { Runnable task = w.firstTask; boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { task.run(); } completedAbruptly = false; } finally { //处理线程退出,也就是后勤打扫 processWorkerExit(w, completedAbruptly); } }
看看 while,说明只要有任务,worker 就埋头苦干,不眠不休。
没有任务的时候,若能做到一直阻塞,就可以防止空闲时线程不销毁。
同理,若想释放线程,是不是跳出 while 循环就可以呢。
确实如此,关键在 ThreadPoolExecutor.getTask()
这个方法,worker 要当心了
毕竟,有时能取到任务,有时半天杵在那了
更有甚者,task 没取到,自己小命都没了。这可能是保活时间到了,方法里面判断当期 worker太多了,要辞退一些。谁轮到谁倒霉,根本不区分核心非核心。也有可能是傍身之地线程池关了,皮之不存,毛将焉附,终落得曲终人散。
详细可以看看 ThreadPoolExecutor.processWorkerExit()
方法
5,现在,你知道线程池怎么调度吗
他虽然手上有一搭worker,但他分配任务的时候,并不会指定,例如1号worker请领取 002任务执行
private final HashSet<Worker> workers = new HashSet<Worker>();
而是创建好了worker,一干人都在那里眼巴巴的等着,来了任务就一窝蜂上去抢。想起了摆渡人书中的场景。
线程池呢,不慌不忙,请来了锁和阻塞队列,让他们不至于抢的不可开交。