ThreadPoolExecutor线程复用与超时销毁的原理

前言

线程池基本上是每个业务都接触的,然而线程池是怎么复用线程,线程是怎么自动超时回收♻️,core核心线程为什么不回收,一直没有过多关注,最近有疑问这些事怎么实现的,偶有所得。

1. 原理分析

其实很早之前,笔者就分析了ThreadPoolExecutor的源码,只是并没有针对线程复用,回收的角度去分析原理,单纯的分析了源码主逻辑。

(65条消息) JDK8线程池-ThreadPoolExecutor源码解析_fenglllle的博客-CSDN博客https://blog.csdn.net/fenglllle/article/details/82790242?spm=1001.2014.3001.5502

关键还是runWorker,其他核心代码见上面的链接。

1.1 线程池线程复用的秘密

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                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);
        }
    }

这里 while (task != null || (task = getTask()) != null) 

可以看出只要能获取task,那么循环就不会结束,这就是线程池的线程复用的秘密,就是让线程在有活干的时候,拼命干活,不休息,😝

那么线程池是怎么回收的呢,其实也很简单,拿不到task就会没活干,就释放了,线程自然运行结束销毁,就是活干完了。

那么线程池如何实现延时回收多余core的线程,又是如何定时回收的呢,如何保证core线程不回收

1.2 线程池线程回收的秘密

关键还是在获取task的代码

    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?
            //是否core线程超时回收或者工作线程超过core
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                //CAS技术
                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;
            }
        }
    }

可以看到allowCoreThreadTimeOut默认是不超时的,当work线程<= core时,就会阻塞当前线程workQueue.take();

boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

 此时就是只剩下核心线程了,默认是不超时回收的,就阻塞在队列这里。

如果>core,那么就超时等待队列获取任务,如果有任务就继续干活😄,否则就标记timeOut为true,在下次循环CAS技术减掉work线程数直接返回,这就是超时回收的原理,没任务了,线程自己结束生命周期了。

验证

上面仅仅是理论分析,实际证明线程复用,超时,核心线程的现象

public class ThreadMain {

    private static final AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2,
                5,
                60,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(5), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("----------haha-mime---------"+atomicInteger.getAndIncrement());
                return t;
            }
        });

        int i=0;
        while (i < 200) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
            i++;
        }

        Thread.sleep(120000);
        System.out.println("=================== end =================");
    }
}

执行后打印,可以说明只创建了5个线程,也可以断点运行,实际启动5个线程,而且启动了5个线程,并且执行了自定义拒绝策略(我直接修改的源码,使用jdk源码需要注入自定义拒绝策略的对象)。

等待一段时间,线程dump,可以看到,线程正在等待超时时间获取任务

 

再等一段时间,dump线程,剩下core线程正被队列阻塞

总结

线程池本质就是很多线程的管理,线程还是遵循生命周期,只是统计了数量,加入了队列,通过队列任务管理来达到复用线程的能力,使用阻塞来操作线程的生命周期,来达到线程的超时销毁、核心线程一直运行的目的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值