zoukankan      html  css  js  c++  java
  • 线程池运行机制

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

     

    朴素的线程入门例子

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

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

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


  • 相关阅读:
    linux基本命令之grep
    Linux在线练习网站
    MySql分表分库思路
    SqlServer触发器
    SqlServer存储过程
    Spring常用注解总结
    SpringMVC的HelloWorld
    XML基础和Web基本概念(JavaWeb片段一)
    红黑树-结点的删除(C语言实现)
    红黑树-结点的插入
  • 原文地址:https://www.cnblogs.com/qingmaple/p/15064051.html
Copyright © 2011-2022 走看看