线程池运行机制

去面试的同学,对线程池的那些参数,大都念念有词。核心线程,阻塞队列,最大线程数,保活时间等。而这些只是冰山一角,背后运行的机制更加精妙有趣,这要从一个朴素的入门例子说起。

 

朴素的线程入门例子

Thread, Runnable,Callable,线程池等。(很长一段时间,我把两个able等同于线程,其实并不是,他们依赖于Thread启动运行,只能算是一个可以执行的任务。在线程池中可以领悟到这个差异)

public class MyTask implements Callable<String>{
   @Override
   public String call() throws Exception {
       return "hello,清汤袭人";
  }
}

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,一干人都在那里眼巴巴的等着,来了任务就一窝蜂上去抢。想起了摆渡人书中的场景。

线程池呢,不慌不忙,请来了锁和阻塞队列,让他们不至于抢的不可开交。

有问题在公众号【清汤袭人】找我,时常冒出各种傻问题,然一通百通,其乐无穷,一起探讨


原文地址:https://www.cnblogs.com/qingmaple/p/15064051.html