线程池1-配置参数 线程池的优雅关闭

目录

 

 

1 ThreadPoolExecutor

核心数据结构

核心配置参数解释

线程池的优雅关闭

1.线程池的生命周期

2.正确关闭线程池的步骤

3.shutdown()与shutdownNow()的区别

shutdown()

shutdownNow()


 

1 ThreadPoolExecutor

核心数据结构

public class ThreadPoolExecutor extends AbstractExecutorService {
    //线程池控制状态ctl是封装了两个概念字段的原子整数
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // 存放任务的阻塞队列
    private final BlockingQueue<Runnable> workQueue;
    // 对线程池内部各种变量进行互斥访问控制
    private final ReentrantLock mainLock = new ReentrantLock();
    // 线程集合
    private final HashSet<Worker> workers = new HashSet<Worker>();
    //...
}

ctl:线程池控制状态ctl是封装了两个概念字段的原子整数,workerCount和runState

workerCount:表示线程的有效数量

runState:指示是否运行、关闭等

 

 

 

每一个线程是一个Worker对象。Worker是ThreadPoolExector的内部类,核心数据结构如下:

private final class Worker extends AbstractQueuedSynchronizer implements
Runnable {
// ...
final Thread thread; // Worker封装的线程
Runnable firstTask; // Worker接收到的第1个任务
volatile long completedTasks; // Worker执行完毕的任务个数
// ...
}

 

由定义会发现,Worker继承于AQS,也就是说Worker本身就是一把锁。这把锁有什么用处呢?用于线程池的关闭、线程执行任务的过程中。

核心配置参数解释

ThreadPoolExecutor在其构造方法中提供了几个核心配置参数,来配置不同策略的线程池。

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

上面的各个参数,解释如下:

  1. corePoolSize:在线程池中始终维护的线程个数。
  2. maximunPoolSize:在corePooSize已满、队列也满的情况下,扩充线程至此值。
  3. keepAliveTime:maxPoolSize 中的空闲线程,销毁所需要的时间,总线程数收缩回corePoolSize。
  4. TimeUnit:单位
  5.  workQueue:线程池所用的队列类型。
  6.  threadFactory:线程创建工厂,可以自定义,有默认值Executors.defaultThreadFactory() 。
  7. RejectedExecutionHandler:corePoolSize已满,队列已满,maxPoolSize 已满,最后的拒绝策略。

下面来看这6个配置参数在任务的提交过程中是怎么运作的。在每次往线程池中提交任务的时候,有如下的处理流程:
步骤一:判断当前线程数是否大于或等于corePoolSize。如果小于,则新建线程执行;如果大于,则进入步骤二。
步骤二:判断队列是否已满。如未满,则放入;如已满,则进入步骤三。
步骤三:判断当前线程数是否大于或等于maxPoolSize。如果小于,则新建线程执行;如果大于,则进入步骤四。
步骤四:根据拒绝策略,拒绝任务。

总结一下:首先判断corePoolSize,其次判断blockingQueue是否已满,接着判断maxPoolSize,最后使用拒绝策略。
很显然,基于这种流程,如果队列是无界的,将永远没有机会走到步骤三,也即maxPoolSize没有使用,也一定不会走到步骤四。

线程池的优雅关闭

线程池的关闭,较之线程的关闭更加复杂。当关闭一个线程池的时候,有的线程还正在执行某个任务,有的调用者正在向线程池提交任务,并且队列中可能还有未执行的任务。因此,关闭过程不可能是瞬时的,而是需要一个平滑的过渡,这就涉及线程池的完整生命周期管理。

1.线程池的生命周期

在JDK 7中,把线程数量(workerCount)和线程池状态(runState)这两个变量打包存储在一个字段里面,即ctl变量。如下图所示,最高的3位存储线程池状态,其余29位存储线程个数。而在JDK 6中,这两个变量是分开存储的。

 

 

由上面的代码可以看到,ctl变量被拆成两半,最高的3位用来表示线程池的状态,低的29位表示线程的个数。线程池的状态有五种,分别是RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED。

下面分析状态之间的迁移过程,如图所示:

线程池有两个关闭方法,shutdown()和shutdownNow(),这两个方法会让线程池切换到不同的状态。在队列为空,线程池也为空之后,进入TIDYING 状态;最后执行一个钩子方法terminated(),进入TERMINATED状态,线程池才真正关闭。

这里的状态迁移有一个非常关键的特征:从小到大迁移,-1,0,1,2,3,只会从小的状态值往大的状态值迁移,不会逆向迁移。例如,当线程池的状态在TIDYING=2时,接下来只可能迁移到TERMINATED=3,不可能迁移回STOP=1或者其他状态。除 terminated()之外,线程池还提供了其他几个钩子方法,这些方法的实现都是空的。如果想实现自己的线程池,可以重写这几个方法:

protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }

2.正确关闭线程池的步骤

关闭线程池的过程为:在调用 shutdown()或者shutdownNow()之后,线程池并不会立即关闭,接下来需要调用 awaitTermination() 来等待线程池关闭。关闭线程池的正确步骤如下:

// executor.shutdownNow();
executor.shutdown();
// 调用shutdown之后,自己写一个循环,每500毫秒检查一次是否停掉了
try {
boolean flag = true;
do {
    flag = ! executor.awaitTermination(500, TimeUnit.MILLISECONDS);
    } while (flag);
} catch (InterruptedException e) {
    // ...
}

awaitTermination(...)方法的内部实现很简单,如下所示。不断循环判断线程池是否到达了最终状态TERMINATED,如果是,就返回;如果不是,则通过termination条件变量阻塞一段时间,之后继续判断。

 public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                // 判断线程池的状态,是否是TERMINATED
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

3.shutdown()与shutdownNow()的区别

1. shutdown()不会清空任务队列,会等所有任务执行完成,shutdownNow()清空任务队列。
2. shutdown()只会中断空闲的线程,shutdownNow()会中断所有线程。

 

下面看一下在上面的代码里中断空闲线程中断所有线程的区别。

shutdown()

shutdown()方法中的interruptIdleWorkers()方法的实现:

参数onlyOne表示 是否仅中断一个空闲线程,false表示中断所有空闲线程

 

 

遍历workers,如果线程没有中断过,并且能获取到锁就中断。

关键区别点在tryLock():一个线程在执行一个任务之前,会先加锁,这意味着通过是否持有锁,可以判断出线程是否处于空闲状态。tryLock()如果调用成功,说明线程处于空闲状态,向其发送中断信号;否则不发送。记得Worker本身就是一把锁,继承AQS。

 

 tryLock()

compareAndSetState(0,1),如果 设置成功,则说明线程空闲,获取到锁,返回true;设置失败说明state不是0,被修改过,也就说明线程正在忙,获取锁失败,返回false; 

 

 

shutdownNow()


shutdownNow()调用了interruptWorkers(); 方法:

interruptIfStarted() 方法的实现:

在上面的代码中,shutdown() 和shutdownNow()都调用了tryTerminate()方法,如下所示:

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }
            // 当workQueue为空,wordCount为0时,执行下述代码。
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // 将状态切换到到TIDYING状态
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        terminated();// 调用钩子函数
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));// 将状态由TIDYING改为TERMINATED
                        termination.signalAll();// 通知awaitTermination(...)
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

tryTerminate()不会强行终止线程池,只是做了一下检测:当workerCount为0,workerQueue为空时,先把状态切换到TIDYING,然后调用钩子方法terminated()。当钩子方法执行完成时,把状态从TIDYING 改为 TERMINATED,接着调用termination.sinaglAll(),通知前面阻塞在awaitTermination的所有调用者线程。所以,TIDYING和TREMINATED的区别是在二者之间执行了一个钩子方法terminated(),目前是一个空实现。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值