线程池中多余的线程是如何回收的

我在网上看到的原话是这样的:线程池中外部的线程是如何回收的?

听说这是一道面试题,我就惊呆了,这不家伙当时就等于问了钱吗?好,现在ThreadPoolExecutor框架都问的这么深?

莫慌。

下面一起来简单分析ThreadPoolExecutor回收工作线程的原理。

第1步

来个例子:

public class ThreadPoolALL {
    public static void main(String[] args) {
        new ThreadPoolALL().ExecutorThreadPool();
    }

    static class TestRunnable implements Runnable {
        static int i = 0;
        @Override
        public void run() {
            synchronized (this) {
                int count = getCount();
                System.out.println(Thread.currentThread().getName() + "  线程被调用了。第" + count + "次");
            }
        }

        public static int getCount() {
            return ++i;
        }
    }

    /**
     * ThreadPoolExecutor是ExecutorSerivce接口的具体实现
     * 即ExecutorSerivce最后也是调用ThreadPoolExecutor的
     * ThreadPoolExecutor提供了很多参数
     * 阿里开发手册建议使用这种方法创建线程池
     */
    public void ExecutorThreadPool() {
        //自定义线程池
        int corePoolSize = 2; //线程池维护线程的最少数量
        int maxPoolSize = 3; //线程池维护线程的最大数量
        long keepAliveTime = 10; //线程池维护线程所允许的空闲时间(解释:当线程池的数量超过corePoolSize时,多余的空闲线程的存活时间。)
        RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();
        BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>(7);
        //或者
        ThreadFactory factory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                SecurityManager s = System.getSecurityManager();
                ThreadGroup group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
                Thread t = new Thread(group, r);
                //设置优先级
                if (t.getPriority() != Thread.NORM_PRIORITY) {
                    t.setPriority(Thread.NORM_PRIORITY);
                }
                //设置错误处理
                t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
                        //自定义处理错误
                        System.out.println("factory捕获了错误--->>>" + t.getName() + e);
                    }
                });
                return t;
            }
        };

        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, queue, factory, rejectedExecutionHandler);
//这是不使用shutdown回收线程的替代方法。
// 在allowCoreThreadTimeOut设置为true时,ThreadPoolExecutor的keepAliveTime参数必须大于0。
        executor.allowCoreThreadTimeOut(false);

        //1、Runnable方法起线程
        TestRunnable testRunnable = new TestRunnable();
        //起多少个线程
        for (int i = 0; i < 10; i++) {
            executor.execute(testRunnable);
        }
//        executor.shutdown();
    }
}复制错误复制成功

 扫VX 领Java资料,前端,测试,python等等资料都有

警告

注意:这里是注释了executor.shutdown()而且 executor.allowCoreThreadTimeOut(false)设置为false(默认为false)

输出:

Thread-0  线程被调用了。第1次
Thread-0  线程被调用了。第2次
Thread-0  线程被调用了。第3次
Thread-0  线程被调用了。第4次
Thread-1  线程被调用了。第5次
Thread-1  线程被调用了。第6次
Thread-1  线程被调用了。第7次
Thread-1  线程被调用了。第8次
Thread-2  线程被调用了。第9次
Thread-0  线程被调用了。第10次复制错误复制成功

但是你看到的是没有退出的,IDEA执行完之后也是没有退出的:

第2步

我们用java命令工具看一下有哪些过程:

C:\Users\HaC>jps
12192
21992 KotlinCompileDaemon
3176 Launcher
10076 Launcher
20476 Jps
20780 ThreadPoolALL
7084复制错误复制成功

可以20780 ThreadPoolALL看到这个java实例已经开始了,

再查看一下这个流程有什么线程:(需要马上查看,因为设置了keepAliveTime)

C:\Users\HaC> jstack 20780
2021-03-23 14:38:03
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):

"DestroyJavaVM" #17 prio=5 os_prio=0 tid=0x0000000002331000 nid=0x5188 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Thread-2" #16 prio=5 os_prio=0 tid=0x0000000020054000 nid=0x3ff8 waiting on condition [0x00000000219fe000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076bcee280> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
        at java.util.concurrent.LinkedBlockingDeque.pollFirst(LinkedBlockingDeque.java:522)
        at java.util.concurrent.LinkedBlockingDeque.poll(LinkedBlockingDeque.java:684)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1066)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:748)

"Thread-1" #15 prio=5 os_prio=0 tid=0x0000000020053800 nid=0x4e70 waiting on condition [0x00000000217df000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076bcee280> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
        at java.util.concurrent.LinkedBlockingDeque.pollFirst(LinkedBlockingDeque.java:522)
        at java.util.concurrent.LinkedBlockingDeque.poll(LinkedBlockingDeque.java:684)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1066)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:748)

"Thread-0" #14 prio=5 os_prio=0 tid=0x00000000200c8000 nid=0x5408 waiting on condition [0x000000002162e000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076bcee280> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
        at java.util.concurrent.LinkedBlockingDeque.pollFirst(LinkedBlockingDeque.java:522)
        at java.util.concurrent.LinkedBlockingDeque.poll(LinkedBlockingDeque.java:684)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1066)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:748)
.....................省略复制错误复制成功

可以看到有三个线程 Thread-1、Thread-2、Thread-0 在TIMED_WAITING状态。

第三步

我在代码中设置了keepAliveTime =10,10秒过完后,再看看这个线程是否还在:

C:\Users\HaC> jstack 20780
2021-03-23 14:40:17
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):

"DestroyJavaVM" #17 prio=5 os_prio=0 tid=0x0000000002331000 nid=0x5188 waiting on condition [0x000
0000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread-2" #16 prio=5 os_prio=0 tid=0x0000000020054000 nid=0x3ff8 waiting on condition [0x00000000
219fe000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076bcee280> (a java.util.concurrent.locks.AbstractQueuedSy
nchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQue
uedSynchronizer.java:2039)
        at java.util.concurrent.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:492)
        at java.util.concurrent.LinkedBlockingDeque.take(LinkedBlockingDeque.java:680)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:748)

"Thread-0" #14 prio=5 os_prio=0 tid=0x00000000200c8000 nid=0x5408 waiting on condition [0x00000000
2162e000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076bcee280> (a java.util.concurrent.locks.AbstractQueuedSy
nchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQue
uedSynchronizer.java:2039)
        at java.util.concurrent.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:492)
        at java.util.concurrent.LinkedBlockingDeque.take(LinkedBlockingDeque.java:680)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:748)复制错误复制成功

 扫VX 领Java资料,前端,测试,python等等资料都有

可以只有两个线程Thread-2、Thread-0 在TIMED_WAITING状态了。

这也验证了:

你如果设置了keepAliveTime御姐设置executor.allowCoreThreadTimeOut(false),当线程池的数量超过corePoolSize时,超过的部分,多余即空闲的线程的存活时间超过keepAliveTime就会被回收。

即maxPoolSize-corePoolSize的部分

如果你executor.allowCoreThreadTimeOut(true),再执行一次,等待 10 秒后,发现线程自动退出了。

Process finished with exit code 0复制错误复制成功

说明executor.allowCoreThreadTimeOut(true)可能corePoolSize的线程也退出了。

下面再讲一下executor.shutdown();,我们放开注释,再设置executor.allowCoreThreadTimeOut(false)

可以看到示意退出了,并不会等待10秒那么长。


总结一下:

线程退出(回收)有两种方法:

1、参数allowCoreThreadTimeOut为true,等待keepAliveTime后,线程会被回收。

2、加上executor.shutdown();马上将线程池中的线程回收。该会调用keepAliveTime参数失效。


这里是参考阅读:

线程池执行过程分析

续上以上的代码,线程池开始执行的入口是这里:

executor.execute(testRunnable);复制错误复制成功

顺藤摸瓜跳过一下这个执行方法:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        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);
        }
        else if (!addWorker(command, false))
            reject(command);
    }复制错误复制成功

这里有一大段注释,我在网上找了一张图,英文大概就是:

文字版本:

  1. 当线程池小于corePoolSize时,新提交的任务将创建一个新的线程执行任务,有可能会出现线程池中存在的线程。
  2. 当线程池达到corePoolSize,新提交任务将被实时工作队列中,等待线程池中任务调度执行
  3. 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
  4. 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
  5. 当线程池中超过corePoolSize,线程时间,时间达到keepAliveTime,关闭开放线程
  6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程开放时间也达到keepAliveTime将

已经执行完了,为什么线程还没有退出呢?

因为线程池的引用被它的内部类 Worker 抓住了。Worker 和线程一对应,是对线程的增强,所以本质上就是因为线程没有被释放。

这里不详细展开线程池的执行原理,需要的可以参考:https : //www.jianshu.com/p/edab547f2710

回收线程的核心在runWorker()方法的getTask()processWorkerExit()

final void runWorker(Worker w) {
    boolean completedAbruptly = true;
    ...
    try {
        while (getTask()...) {
            ...
            处理任务
        }
        //该线程已经从队列中取不到任务了,改变标记,该标记表示:该线程是否因用户因素导致的异常而终止
         completedAbruptly = false;
    } finally {
        //线程移除
        processWorkerExit(w, completedAbruptly);
    }
}复制错误复制成功
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);
    } finally {
        mainLock.unlock();
    }

    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        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);
    }
}复制错误复制成功

空闲,简单顺着代码分析,最后线程池方法在执行关闭方法或允许CoreThreadTimeOut 时,会调用interruptIdleWorkers 方法来中断线程池的线程,interruptIWorkers 方法会使用tryLock 方法来判断线程中的线程是否是允许状态;

详细的可以参考:

线程池什么时候调用shutdown方法_一篇文章搞懂线程池_weixin_39633954的博客-CSDN博客

线程池运行原理分析 - 简书


参考这里的总结:https : //www.cnblogs.com/kingsleylam/p/11241625.html

4.总结

ThreadPoolExecutor回收工作线程,一条线程getTask()返回null,就会被回收。

分两种场景。

4.1 未调用shutdown() ,RUNNING状态下全部任务执行完成的场景

线程超时corePoolSize,线程超时,超时后CAS退出工作线程数,如果CAS,返回,线程回收成功。另外进入下一次循环。当生命线程数量小于corePoolSize,就可以一直持续了。

 扫VX 领Java资料,前端,测试,python等等资料都有

4.2 调用shutdown() ,全部任务执行完成的场景

shutdown() 会向所有线程发出中断信号,可能有两种可能。

4.2.1所有线程都在阻塞

中断,唤醒,进入循环,都符合第一个判断条件,都返回无效,所有线程回收。

4.2.2任务还没有完全执行完

至少会有一条线程被回收。在processExit(Worker w,boolean CompleteAbruptly)里会调用tryTerminate(),向任意一个线程发出中断信号。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值