Java Thread&Concurrency(11): 深入理解ThreadPoolExecutor及其实现原理

背景(注释):

一个ExecutorService通过使用不同的线程池来执行提交的任务,线程池可以通过Executors来配置。

线程池主要解决两个问题:改善执行大量任务时的性能(通过一个任务/调用的模式)和提供资源方面(比如线程、执行的任务总数统计)的限制和管理。

Executors提供了几种常用的执行器:

  • Executors.newCachedThreadPool:无限制线程池,自动线程重用。
  • Executors.newFixedThreadPool:固定数量线程池。
  • Executors.newSingleThreadPool:单个工作线程。
以上的这些线程可以满足常用的场景了。

饱和策略:
当心的任务通过execute提交之后,假如当前执行器已经被关闭,但是当前还有工作线程以及队列中还有待执行任务。那么我们的会执行RejectedExecutionHandler的rejectedExecution方法,有四种策略如下:
  • 默认的拒绝策略(AbortPolicy),通过抛出一个RejectedExecutionException异常来拒绝。
  • (CallerRunsPolicy),通过调用者自己执行任务来避免任务太多。
  • (DiscardPolicy),直接忽略任务。
  • (DiscardOldestPolicy),如果当前执行器还没有关闭,那么当前队列中存在最久的任务会被忽略,然后重试。

扩展:
以下是一个有趣的执行器,通过它我们可以在外部暂停/重启执行器的工作

class PausableThreadPoolExecutor extends ThreadPoolExecutor {
    private boolean isPaused;
    private ReentrantLock pauseLock = new ReentrantLock();
    private Condition unpaused = pauseLock.newCondition();
 
    public PausableThreadPoolExecutor(...) { super(...); }
 
    protected void beforeExecute(Thread t, Runnable r) {
      super.beforeExecute(t, r);
      pauseLock.lock();
      try {
        while (isPaused) unpaused.await();
      } catch (InterruptedException ie) {
        t.interrupt();
      } finally {
        pauseLock.unlock();
      }
    }
 
    public void pause() {
      pauseLock.lock();
      try {
        isPaused = true;
      } finally {
        pauseLock.unlock();
      }
    }
 
    public void resume() {
      pauseLock.lock();
      try {
        isPaused = false;
        unpaused.signalAll();
      } finally {
        pauseLock.unlock();
      }
    }
  }

实现:

由于这个实现并不复杂,所以我们仅分析execute操作和证明--不可能存在队列中的任务残留。

首先来看execute:

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        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);
    }
事实上这里的注释已经说得很清楚了,详情如下:

  • 首先如果工作线程数小于corePoolSize,那么试着添加一个新的工作线程来执行任务。这个过程会检查运行状态和工作者线程数,从而避免错误或者过多的工作者线程。
  • 假如工作线程已满,那么我们就提交任务至队列,接着判断此刻的执行器状态,假如已经被关闭就尝试删除该任务并拒绝,否则假如此刻不存在工作线程就创建一个。
  • 否则在工作列队已满的情况下可以查看是否可以创建一个新的工作线程,不行则拒绝任务。


队列中不会残留任务:

如上代码所示,假如存在一种情景:队列中还有任务,但是再也不会有工作队列去执行。我们来看看如何产生。

首先可以显然看出,SHUTDOWN状态不会对已经提交到队列的任务产生任何影响。

  • 假如任务的提交是作为addWorker的方式成功调用,那么必定被执行。
  • 假如任务是提交至队列,并被工作线程得到(take或者poll)那么必定被执行
  • 假如任务提交至队列之后,注意当当前工作线程数为0,则会通过addWorker方法,若成功则任务必定会被执行,若失败则说明有其他的调用产生了工作线程或者执行器终止(STOP),前者会执行任务,后者不管。
  • 所以仅在工作线程的poll限时操作没有注意到存在任务,并且提交任务的调用发现还存在工作线程,在这种情况下,这个工作线程就会错失任务的信号。
  • 所以最后的关键就是processWorkerExit方法,在其中会再次判断当前的队列状态,从而能够再次生成一个工作线程来探测任务。
  • 事实上,执行器在shutdown之前会稳定维护着1个或者corePoolSize个工作线程(前提是达到过这么多工作线程)。



 
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值