On Java TheadPool

87 阅读7分钟

简介

线程池的概念,已经布满在众多的open source之中(jdk/tomcat/数据库连接driver/netty/zio/spring/akka/play/kafka/guava),用来尽可能的发挥计算机的多核处理能力,进而提高应用的效用,满足使用特殊的场景。此处仅就jdk ThreadPool进行说明

数据结构的分析

  1. jdk线程池,实际上是已有并发工具类的集合。
工具类作用
ReentrantLock mainLock确保数据修改能够正确进行 包括ctl workers
BlockingQueue workQueue线程池中,无法不能再创建新线程的时候,用来存放暂时来不及处理的Runnable
HashSet workers这里的workers,用来存放所有尚未完成的任务,每次修改数据,必须要与mainLock配合,才能保证安全

以上三者主要表达了--安全与数据存放

  1. 线程池用来表达自身相关的数据
  • 该数据分作两类, 状态数据(status)/尚未完成的任务数(workerCount)
数据类型说明
状态数据(status)规定了四种可能 RUNNING/SHUTDOWN/STOP/TIDYING/TERMINATED,不同的状态,定义了不同行为
尚未完成的任务数(workerCount)这个数据主要用于判断,小于线程池的可用线程时,创建新线程。否则放入到BlockQueue中
  • 线程池用了一个int变量clt,来包括了这两类数据。 int数据有32bit.最高的3bit用作表示status,后29bit表示workerCount。 然后用|运算,将两个数据合并起来。 即xxx_yyyyyyyyyyyyyyyyyyyyyyyyyyyyy
  • status的各种语义
status二进制形式action semantic
RUNNING111_00000000000000000000000000000 高3bit 全是1 是唯一存在的负数正常运行,同时此时clt是绝对小于0的,因而clt>=0时,说明线程池的status已不再是RUNNING
SHUTDOWN000_00000000000000000000000000000 高3bit 全是0不再接受新的任务了,但依旧要处理queue中,剩余的任务
STOP001_00000000000000000000000000000不再接受新的任务了,同时也不再处理queue中,并打断所有在运行的任务
TIDYING010_00000000000000000000000000000所以任务已经结束,workerCount=0,然后去调用hook方法terminated(),进入最后的状态
RUNNING011_00000000000000000000000000000此时线程池已经终灭了
  • 位运算之mask
位运算说明
status只计算前3bit,则需要高3bit都是1,其余是0的数据,clt进行&运算。即111_00000000000000000000000000000,也就是RUNNING
workerCount只计算后29bit,则需要高3bit都是0,其余是1的数据,clt进行&运算。即000_11111111111111111111111111111

令000_11111111111111111111111111111=(1<<29-1)=COUNT_MASK(意在获取workerCount) 则~COUNT_MASK=111_00000000000000000000000000000 可以计算status

以上就是基本的数据内容

On Worker

Worker的数据结构可以描述为Worker(firstTAsk,thread,state)

  1. firstTask
  • Worker有一个指针firstTask. 代表他可以执行的第一个任务 image.png
  • 在worker执行run方法的时候,调用了runWorker image.png
  • 第一个任务之后,就从workerQueue中去拉取task
  1. Woker实现了同步队列,要在借用同步队列的state,防止在worker在尚未开始的时候,被打断。同时保证自己的线程安全,独占模式
  • 构造器中
Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}
  • 在runWorker的时候,worker先得让state归0。然后才能再去lock/unlock image.png
  • runWorker之末,finally 代码块,会处理worker的退出。前提是先退出while循环。
  • 退出while循环条件 task=null 同时 getTask()=null。表明当前worker没有task可用,则应当回收当前worker.此processWorkerExit之所谓也。

3.processWorkerExit worker退出。出现异常的时候 completedAbruptly=true

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w); // 移除worker
    } finally {
        mainLock.unlock();
    }
    
    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) { // 有RUNNING/SHUTDOWN两种可能 因为这两种状态才可以处理queue中任务
        if (!completedAbruptly) {
            // 核心线程是否存在时效  存在的时候 可以将所有的core thread都回收 
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 核心线程有时效限制,同时workQueue还有任务 
            // 则值最少应该保留一个core thread继续执行 剩下任务
            if (min == 0 && ! workQueue.isEmpty()) 
                min = 1;
            // 如果worker的数目 超过最小需求 则直接返回    
            if (workerCountOf(c) >= min)
                return; // replacement not needed 针对的是 addWorker(null, false); 这个代码
        }
        //此时不满足 if (workerCountOf(c) >= min)这个条件completedAbruptly=true 即workerCount<min,需要对worker进行补足
        addWorker(null, false); 
    }
}
  1. 根据以上的分析,可以得出的结论是,worker处于独占模式地,不断执行可以执行的任务,同时按照参数配置,考虑worker是否进行回收。

线程池的入口

线程池的入口方法,主要是提交任务。而实际上,要么创建worker/要么将任务暂时存入到blockQueue中/被拒绝。 以execute方法为例

image.png

action说明
addWorker不能超过规定的核心的线程资源。
queue.offer超过规定的可用线程资源,先放入queue中
rejected添加queue不成,同时有超过max可用核心线程资源,那便reject掉

addWorker

可以分为几个步骤

  1. 检查线程池状态,主体是一个循环
retry:
for (int c = ctl.get();;) {
   // Check if queue empty only if necessary.
   if (runStateAtLeast(c, SHUTDOWN)
       && (runStateAtLeast(c, STOP)
           || firstTask != null
           || workQueue.isEmpty()))
       return false; // 线程池状态不对

   for (;;) {
       if (workerCountOf(c)
           >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
           return false; // worker数量超过了可用的线程资源
       if (compareAndIncrementWorkerCount(c)) //修改ctl成功 跳出循环
           break retry;
       c = ctl.get();  // Re-read ctl
       if (runStateAtLeast(c, SHUTDOWN))
           continue retry; // 线程池不在RUNNING状态
       // else CAS failed due to workerCount change; retry inner loop
   }
}
  1. 线程安全添加worker
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask); 
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int c = ctl.get();

                if (isRunning(c) ||
                    (runStateLessThan(c, STOP) && firstTask == null)) {
                    if (t.getState() != Thread.State.NEW)
                        throw new IllegalThreadStateException();
                    workers.add(w); // 添加新建的worker
                    workerAdded = true;
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                container.start(t); // 启动线程池创建的线程t
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w); // 尚未启动 被认定为失败
    }
    return workerStarted;
}

getTask

主体也是循环 可以分为以下步骤

  1. 线程池状态检测
  2. 判断当前worker能否退出,被回收
  3. 拉取任务
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();

        // 第一步 检测状态
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling? 
        // 第二步 worker是否可以退出了
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        
        // 第三步 拉取任务
        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
  1. 关于一个if条件的说明 worker是否可以退出
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

if ((wc > maximumPoolSize || (timed && timedOut))
    && (wc > 1 || workQueue.isEmpty())) {
    if (compareAndDecrementWorkerCount(c))
        return null;
    continue;
}

流程图大抵如此

image.png

从图中可以看出 1 当wc>maxPoolSize,但还是要确保退出后,至少还有一个worker,也就是说(wc>1),这时自然希望当前worker可以退出,以保持线程池定义的约束。 2 boolean timed => 核心线程支持超时退出(allowCoreThreadTimeOut)或者 wc>corePoolSize (此时核心线程不支持超时退出,但是可以退出核心线程以外的线程) 3 timedOut 间接表明了 workerQueue里面没有数据 拉取的任务是null 此时可以回收多余的worker

image.png

4 因此timed&&timeOut 表示worker可以进行回收,worker类型包括了核心和非核心