源码出发,掌握线程池中线程的生命周期

135 阅读6分钟

P-10259032-10239559.jpg

我们知道线程池中有三个要素,线程/任务/等待队列,任务需要被执行,线程执行任务,但是线程资源是有限的,所以需要等待队列存放任务。

线程池的执行流程

  1. 首先会判断线程池的状态是否为Running,只有Running状态下才允许提交任务。
  2. 会判断工作线程数是否小于核心线程数,如果小于,会新建一个线程去执行任务。
  3. 如果工作线程数大于核心线程数但小于最大的线程数,且此时等待队列还没有满时,任务会进入等待队列中去。
  4. 如果工作线程数大于核心线程数但小于最大的线程数,且等待队列已经满了,这个时候会新建一个线程去执行任务。
  5. 如果工作线程数已经达到最大线程数,且这个时候等待队列也满了,这个时候会根据拒绝策略处理任务,默认的拒绝策略AbortPolicy丢弃任务,报异常。

线程执行任务 Worker对象

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** 构造方法 */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
}

从上知,线程池通过Worker类将任务和线程绑定,Worker类实现了Runnable接口,继承了AQS,我们接着从源码看线程池是如何通过Worker类来执行任务的。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    // 获取Worker对象中的任务
    Runnable task = w.firstTask;
    // 将Worker对象中的任务置为null
    w.firstTask = null;
    // 释放锁
    w.unlock();
    boolean completedAbruptly = true;
    try {
        // 从getTask()中不断获取任务,直到空跳出循环,进入processWorkerExit()销毁工作线程
        while (task != null || (task = getTask()) != null) {
            w.lock();
            ......
            try {
                ......
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } 
                ......
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

线程池不断通过while()循环从getTask()获取task任务,通过task.run()执行,这里其实就是线程的执行。那线程池是如何保护任务的正常执行的呢?是的,AQS独占锁,w.lock(),每次获取任务,就上一把锁,执行完毕后释放锁,通过锁机制来保护任务的执行。

线程获取任务 解析getTask()

private Runnable getTask() {
    boolean timedOut = false; 
    for (;;) {
        // 当前的工作线程数
        int wc = workerCountOf(c);
        // 允许核心线程超时 || 工作线程数大于核心线程数
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
        // 走workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)有两种情况
        // 1、要么允许核心线程失败
        // 2、要么当前工作线程数大于核心线程数
        
        // 走workQueue.take()
        // 当前的工作线程数等于核心线程数,且不允许核心线程数超时,通过workQueue.take()进行阻塞。
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
            // 如果任务不为空,返回任务
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
workQueue.pool(keepAliveTime, TimeUnit) 从队列中获取元素,当阻塞时间keepAliveTime到了还没获取元素会返回null
workQueue.take()一直阻塞直到获取元素或线程中断

线程池不断的将队列中的任务pool()出来执行,当在超时时间内仍然无法获取任务即任务被执行完了,会返回一个空任务,返回runWorker方法跳出循环,回收空闲线程。 当回收到corePoolSize阈值时,这个时候会通过allowCoreThreadTimeOut来判断是否需要通过take()方法来保证剩下线程的存活。

线程的回收 processWorkerExit()

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 如果是异常完成,通过CAS workCount - 1
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    // 加锁,因为works所属的HashSet不是线程安全的,所以需要加锁控制
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        // 从works从移除空闲的work线程
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    // 尝试去终止线程池
    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            // 如果允许核心线程销毁,min = 0,否则min=核心线程数
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
            // 如果队列不为空,还有任务待处理,min = 1
                min = 1;
            if (workerCountOf(c) >= min)
            // 不需要addWorker直接返回
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

线程池维护了一个类型为Worker的集合(HashSet),通过将Worker从集合中移除,达到回收线程的目的。如果线程数被回收为0了,假设这个时候我的队列中仍然存在一个任务咋办?这时线程池会通过addWorker()方法去新建一个线程处理任务。所以线程池中线程数为空的条件不是所有的线程都空闲,而是等待队列里是否存在任务。

如果停止一个线程池 shutdown()\shutdownNow()

线程池提供了两个命令来让我们去停止线程池

  • shutdown():会等待目前正在执行的任务执行好后,关闭线程池,是一种柔性中断。
  • shutdownNow(): 会中断正常执行的任务,关闭线程池,将任务通过drainQueue()转移到一个List,便于后续做一些补救措施,是一种刚性中断。
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 检查关闭权限
        checkShutdownAccess();
        // 将线程池的状态变更为SHUTDOWN(任务可以继续执行,但是不接受新的任务)
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}
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();
    }
}
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 检查关闭权限
        checkShutdownAccess();
        // 将线程池的状态变更为STOP(中断任务,不再接受任务)
        advanceRunState(STOP);
        // 中断所有工作线程
        interruptWorkers();
        // 将阻塞队列中的任务,移动到task集合中
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

线程中断的原理 详解interrupt()和interrupted()的差别

  • interrupt() thread.interrupt()是直接中断线程吗?答案是 【不是】,只是通过interrupt0()给调用这个方法的线程设置中断状态,不直接中断
  • interrupted() 检查当前线程的中断标志位,同时清除中断标志,改为false, 这里需要明确的是当前线程,而不是调用线程。
public void interrupt() {
    if (this != Thread.currentThread())
        // 如果不是thread本身需要检查其权限
        checkAccess();
    
    // 对象锁控制并发
    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            // 只是设置中断标志
            interrupt0();  
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

线程池中的锁的作用 mainLock

  • addWorker() 保护工作线程集合避免并发增加工作线程、保护度量统计数据变更
  • interruptIdleWorkers() 保护工作线程中断的串行化,避免"中断风暴"
  • tryTerminate() 保证状态TIDYING -> TERMINATED,钩子方法terminated()回调和条件变量唤醒

参考资料

  1. 了解下JUC的线程池学习十一(理解可重入锁mainLock成员变量)
  2. Java线程池实现原理及其在美团业务中的实践

最后

自我总结,如果有错误或不对之处,请随时留言指出,如果有职业上的问题交流可以加我的VX:DOUFOR,很高兴能与你进行技术、生活且不限于方向上的交流。