Java 基础 - 线程池是如何回收空闲线程的 ?

说明

线程池如何使用?它是如何回收空闲线程的?这类问题可能在面试中经常遇到。本篇博文我将通过源码来对线程池提交任务及回收空闲线程部分的内容进行学习总结。

我们都知道在向线程池提交任务时,会有以下三种情况:

  1. 如果线程池内运行的线程数小于设置的 corePoolSize 值,不论是否有空闲线程都会新创建一个线程执行该任务
  2. 如果线程数已到达 corePoolSize 值,则会将任务放入任务队列
  3. 当任务队列已满,并且线程数小于设置的 maximumPoolSize 值,则会新创建线程执行该任务,否则将执行设置的拒绝策略

在线程池处理完任务后,会根据设置的 keepAliveTime 来回收核心线程数 corePoolSize 之外的线程,同时若设置了 allowCoreThreadTimeOut 值为 true,也会对核心线程进行回收。

正文

提交任务

这部分代码就是根据以上说明的步骤进行操作的。但需要注意的是,在任务进入任务队列时进行了两次状态检查,这样做的原因是在入队后线程池状态可能发生变化,如 线程池主动停止,这时就需要将任务从队列中删除;线程池中唯一的线程停止,就需要重新添加 woker 来处理任务。

 public void execute(Runnable command) {
    if (command == null)
    int c = ctl.get();
    // 线程数量小于核心线程数 直接新添加 worker
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 第一次检查运行状态
    if (isRunning(c) && workQueue.offer(command)) {
        // 入队成功后进行第二次检查
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 队列已满或队列状态发生改变,尝试添加新 worker
    else if (!addWorker(command, false))
        reject(command);
}

添加新 worker

通过调用 addWorker(Runnable task, boolean core) 方法来添加新的工作线程。该方法可以分成两部分,在第一部分通过循环判断线程池的运行状态及完成线程数量的修改。

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

    // Check if queue empty only if necessary.
    // 当线程池已关闭或正处于关闭过程,没有新任务且任务队列已空时,不需要再添加新 worker
    if (rs >= SHUTDOWN &&
        ! (rs == SHUTDOWN &&
           firstTask == null &&
           ! workQueue.isEmpty()))
        return false;

    for (;;) {
        int wc = workerCountOf(c);
        // 判断线程数量是否超过限制
        if (wc >= CAPACITY ||
            wc >= (core ? corePoolSize : maximumPoolSize))
            return false;
        // 通过 CAS 修改线程数量 进行 +1
        if (compareAndIncrementWorkerCount(c))
            break retry;
        c = ctl.get();  // Re-read ctl
        // 数量修改失败重新获取运行状态,若状态变化则循环重试
        if (runStateOf(c) != rs)
            continue retry;
        // else CAS failed due to workerCount change; retry inner loop
    }
}

在成功修改线程数量后,进行 worker 的创建和启动,并且使用锁进行 worker 的添加

为什么要进行同步添加?
ThreadPoolExecutor 类内部使用 HashSet 保存所有的 worker,HashSet 是非线程安全的,同时线程池还维护了最大线程池大小值(largestPoolSize),该值与 Set 大小有关,所以需要同步添加获取。

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
    // 创建新 worker
    w = new Worker(firstTask);
    final Thread t = w.thread;
    if (t != null) {
        // 同步添加
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            int rs = runStateOf(ctl.get());
            if (rs < SHUTDOWN ||
                (rs == SHUTDOWN && firstTask == null)) {
                if (t.isAlive()) // precheck that t is startable
                    throw new IllegalThreadStateException();
                workers.add(w);
                // 修改 largestPoolSize 值
                int s = workers.size();
                if (s > largestPoolSize)
                    largestPoolSize = s;
                workerAdded = true;
            }
        } finally {
            mainLock.unlock();
        }
        if (workerAdded) {
            // 启动worker
            t.start();
            workerStarted = true;
        }
    }
} finally {
    // worker 启动失败机型回退 集合中删除worker 修改数量等
    if (! workerStarted)
        addWorkerFailed(w);
}
return workerStarted;

创建 worker

Worker 是 ThreadPoolExecuter 的内部类,继承了 AQS 并实现了 Runnable 接口。

 private final class Worker extends AbstractQueuedSynchronizer implements Runnable

在创建 worker 时,根据给定任务创建线程,这里 worker 新创建线程,并将自身作为任务参数。

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

所以在启动 worker 时,由 worker 创建的线程执行 worker 自身的 run 方法,而 run 方法中又调用 ThreadPoolExecuter 的 runWorker 方法,真正获取执行用户提交的任务。

public void run() {
    runWorker(this);
}

运行 worker

在 worker 运行时,会通过循环调用 getTask 方法获取任务进行处理。在该方法中会进行超时判断,若返回的任务为 null,则跳出循环通过 processWorkerExit 方法执行线程回收。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    // 是否意外退出的标志 在回收时会根据该值判断是否需要补充 worker
    boolean completedAbruptly = true;
    try {
        // 循环获取任务,返回 null 时结束循环
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // 判断线程池运行状态 及时响应中断
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                // 完成任务数统计
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 执行线程回收 处理线程退出
        processWorkerExit(w, completedAbruptly);
    }
}

获取任务

在 getTask 方法中,当线程超时是通过循环 + CAS 来实现 worker 数的减少。

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

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

        // 判断线程池的运行状态
        // 如果线程池已经开始关闭或者已经处于关闭,任务队列为空时 worker 数 -1
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            // 循环 + CAS
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // 判断是否需要超时限制
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 当线程获取任务超时,修改 worker 数
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            // CAS 修改
            if (compareAndDecrementWorkerCount(c))
                return null;
            // CAS 修改失败后循环重试
            continue;
        }

        try {
            // 超时时间为设置的 keepAliveTime 值
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

通过源码可以看到空闲线程是否需要回收时是根据获取任务是否超时判断的,而超时时间就是设置的 keepAliveTime 值。当获取任务超时时先进行了 worker 数的修改,返回的任务为 null,随后跳出任务获取处理流程,进入线程回收流程。

回收 worker

调用 processWorkerExit 方法进行 worker 回收,在该方法中实现了完成任务数的统计和 worker 从 set 集合中的移除,并且根据 completedAbruptly 参数判断是否需要 worker 补充。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 如果是意外退出任务处理流程 将 worker 数 -1
    if (completedAbruptly)
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 加锁实现完成任务数的统计及队列移除
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    // 尝试终止线程池
    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        // 如果线程池还处于运行中,并且线程是正常退出 则判断是否需要补充新 worker
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

总结

通过源码我们可以看到,在线程池提交任务后,会通过内部的 worker 线程进行任务的处理。在 runWorker() 方法中 worker 会通过循环获取任务,对于有超时限制的 worker,会在获取方法 getTask() 中进行超时判断,若判断已超时也就是空闲线程已到最大存活时间,则进行 workre 数的修改并返回空任务。在获取到空任务时,会跳出循环进行 worker 回收的方法 processWorkerExit(),在该方法中加锁进行完成任务数的统计及 worker 从 set 中的移除,最后判断是否需要进行 worker 的补充。在实现 worker 数值修改时时通过循环 + CAS 的方式实现的。

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值