ThreadPoolExecutor源码剖析的一些思考和总结

最近迁移一个项目,涉及到线程池的问题。特意看了一下ThreadPoolExecutor源码,记录一下。思考几个问题

ThreadPoolExecutor的基本实现思路是什么?

这里写图片描述
- 在线程池正在运行(running)的状态下,提交任务到线程池以后,如果线程数量小于corePoolSiez,则创建新的工作线程加入workers中,并启动该线程;如果大于等于corePoolSiez,则加入任务队列中,等待工作线程空闲的下来再处理这些任务。如果加入队列也失败,则最后尝试新建工作线程处理。

  • 在线程池正在关闭(shutdown)的状态下,拒绝提交的任务到线程池,中断所有空闲的线程。这里空闲的线程指那些等待任务执行的线程,也就是在getTask方法中阻塞或者等待的线程。

ThreadPoolExecutor的数据结构大概是怎么样的?

用int类型来记录ThreadPoolExecutor的内部属性,一个int整型有32位,其中高前3位表示ThreadPoolExecutor的5种运行状态,低29位保存ThreadPoolExecutor的工作线程数量。

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

5种运行状态如下。

  • RUNNING:正常运行状态;
  • SHUTDOWN:线程池关闭状态,调用shutdown设置的状态;
  • STOP: 线程池停止状态,调用shutdownNow设置的状态
  • TIDYING:用来继承ThreadPoolExecutor后提供扩展用的,顾名思义,TIDYING表示整理,也就是线程池终止前进行整理。
  • TERMINATED:线程池终止状态。
    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

2个重要的结构。workers是工作线程容器,里面的线程就像一个不知疲倦的工人,不停的从任务队列中提取任务执行。workQueue是工作任务队列,当工作线程来不及处理提交的任务时,就把任务放入队列中。

    private final HashSet<Worker> workers = new HashSet<Worker>();

    private final BlockingQueue<Runnable> workQueue;

几个重要的属性

  • corePoolSize:工作线程核心数量
  • maximumPoolSize:工作线程最大数量
     private volatile int corePoolSize;

    private volatile int maximumPoolSize;

任务怎样提交到线程池中?

一般调用execute方法提交任务到线程池中.
这里写图片描述

addWork方法主要是增加工作线程,首先根据线程池状态和设定容量是否允许增加,如果允许,加入工作线程容器中,然后启动该线程。
这里写图片描述

这里写图片描述

接下来我们返回来看addWork失败后会怎么样:先尝试加入任务队列,加入成功后进行double-check。如果加入队列后线程池已经shutdown,这是需要重新拒绝这个任务;
如果线程池中没有工作线程存活,则需要重新增加工作线程。否则直接拒绝任务。

        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);

任务怎样执行?

提交的任务是在工作线程中执行的。所谓的工作线程就内部类Work。这里要注意一个概念。工作线程和任务是不一样的。看起来好像都是线程的概念,但却大不相同。工作线程是是执行任务的线程,通过Thread.start启动运行的;而任务是由外部的业务代码组成,就是一个run方法而已。
这里写图片描述

工作线程是委托runWorker运行,我们跟进去看看。
主要实现是While无限循环,如果是第一次运行则执行firstTask,否则不断从任务队列中获取任务执行。
这里写图片描述

那什么时候runWorker会跳出循环终止?getTask获取到null时,跳出循环。返回null有两种情况,
1.线程池停止;
2.线程池关闭并且任务队列为空;
3.工作线程超出设定的maximumPoolSize;
4.当(allowCoreThreadTimeOut || workerCount > corePoolSize) == true并且工作线程等待任务超时。
这里写图片描述

跳出循环后,工作线程会进行退出处理
这里写图片描述

怎么关闭线程池?

ThreadPoolExecutor提供了shutdown和shutdownNow两种方法关闭线程池。因为java中断无法做到抢断,而是协作式的中断,所以如果你的任务忽略中断,shutdown和shutdownNow效果是一样的。

  • shutdown中断空闲的工作线程,等待工作任何队列处理完后再返回;
  • shutdownNow直接中断所有工作线程,不等待任务队列处理完,返回任务队列。
    这里写图片描述

从细节看,interruptIdleWorkers每次中断线程的时候,首先会尝试条用tryLock获取锁,如果获取失败,说明这个线程并不空闲,正在处理其他任务,则不中断该线程;
这里写图片描述

而interruptWorkers最终调用interruptIfStarted方法,不会调用tryLock获取方法,简单暴力的关闭了线程。

    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

    //Worker中方法
            void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }

shutdown什么时候才处理完任务队列?可以用awaitTermination方法,等待termination信号才退出或者超时退出。

    public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

由此可见,优雅停止线程池方式是

....
executor.shutdown();
executor.awaitTermination(timeout, TimeUnit.SECONDS);
...
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值