线程池- Java

181 阅读3分钟
  • 线程池是怎么实现核心线程保活的?
  • 非核心线程超时销毁时怎么保证不误杀正在执行任务的线程

execute的调用链路

ThreadPoolExecutor.execute(Runnable command)
  → addWorker(Runnable firstTask, boolean core)
    → Worker.start()
      → Worker.run()
        → ThreadPoolExecutor.runWorker(Worker w)
          → getTask() 

当线程池excute的时候,最终会getTask:getTask作用

核心线程保活调用workQueue.take(),阻塞等待新任务
非核心线程超时调用workQueue.poll(keepAliveTime, unit),超时返回null触发线程回收
  1. 线程启动后:首次通过getTask()获取初始任务(firstTask
  2. 任务执行完成后:循环调用getTask()获取新任务
  3. 线程销毁前:当getTask()返回null时,触发processWorkerExit()
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

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

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // 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;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : //非核心
                workQueue.take(); //核心存活
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

非核心线程使用poll(keepAliveTime,...)获取任务

两种结果: 超时前获取到任务:继续执行任务,重置存活时间 超时未获取任务:执行compareAndDecrementWorkerCount()减少工作线程数并终止线程

  1. 只有空闲线程才会进入getTask()的poll/take逻辑
  2. 正在执行任务的线程处于运行状态,不会检查超时

线程池的Worker类:继承AQS将其作为不可重入的互斥锁使用:

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;
       
   
    // 锁状态定义
    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) { // 通过CAS获取锁
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false; // 实现不可重入性
    }
    
    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0); // 释放锁
        return true;
    }
    .....
}
  • ## 不可重入的互斥锁:防止任务执行期间被中断
    线程池在关闭时需要中断空闲线程,但正在执行任务的线程不能被中断(否则可能导致任务数据不一致)。

AQS:state

  • 锁被持有state=1):线程正在执行任务(runWorker()中运行用户代码)
  • 锁未被持有state=0):线程正在getTask()中等待新任务(即空闲状态)

线程池的5种状态

状态含义
RUNNING111正常运行状态
SHUTDOWN000不再接收新任务,处理队列任务
STOP001中断所有任务,不处理队列
TIDYING010所有任务终止,即将执行terminated()
TERMINATED011完全终止

关键行为控制:

  1. RUNNING

    • 允许创建新线程
    • 允许任务入队
  2. SHUTDOWN

    • 拒绝新任务(execute()直接返回false)
    • 继续处理队列任务
    • getTask()返回null触发线程退出
  3. STOP

    • 中断所有工作线程
    • 清空任务队列
    • 立即进入TIDYING状态

状态转换时的队列唤醒

public void shutdown() {
    advanceRunState(SHUTDOWN);
    interruptIdleWorkers(); // 中断所有在take()阻塞的线程
    onShutdown();
}

interruptIdleWorkers()
→ Worker.tryLock()获取锁成功(说明线程正在getTask()阻塞)
→ thread.interrupt()
→ workQueue.take()抛出InterruptedException
→ getTask()返回null
→ runWorker()退出循环 
try{
    while (task != null || (task = getTask()) != null)
}finally{
    processWorkerExit(w, completedAbruptly);
}
→ processWorkerExit()回收线程
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}