Java多线程(七) ——线程池 ThreadpoolExecutor 的底层实现

  一.ThreadpoolExecutor 的设计

  1.  我们可以把需要执行的任务理解为生成者,线程池理解为消费者,来看线程池的设计.

    

   

    (1)需要调度的任务在run()方法中编写具体逻辑,然后放入一个阻塞队列中,被线程池调用时执行,未被调用时处于阻塞状态。

    (2)要是队列满了怎么办?表示核心线程不足,处理不过来,可以找临时线程帮忙。

    (3)临时线程也不能无限制的创建所以需要设定最大线程数 

    (4)当队列满了,也达到了最大线程数,就需要策略来进行处理(比如抛异常拒绝继续接收)

    (5)当队列空了,或者临时线程拿不到任务了,证明已经不需要帮忙了。所以我们需要设定存活时间在过期时间内还没有任务,临时线程就销毁。  

    (6)线程池中线程的创建可以通过一个工厂方法

2(ThreadpoolExecutor 有多个重载的构造方法,它最完整的构造方法中每个参数如)

corePoolSize核心线程数量
maximumPoolSize最大线程数
keepAliveTime超时时间,超出核心线程数量以外的线程空余存活时间
unit存活时间单位
workQueue保存执行任务的队列
threadFactory创建新线程使用的工厂
handler当任务无法执行的时候的处理方式

 

 

 

 

 

 

 

3.任务缓存队列及排队策略

即 workQueue,它用来存放等待执行的任务。 workQueue 的类型为 BlockingQueue,通常可以取下面三种类型:

ArrayBlockingQueue基于数组的先进先出队列,此队列创建时必须指定大小
LinkedBlockingQueue基于链表的先进先出队列,如果创建时没有指定此队列大小,则默 认为 Integer.MAX_VALUE
SynchronousQueue这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个 线程来执行新来的任务

二、ThreadpoolExecutor的底层实现

 1.execute(Runnable command)

     ctl它是一个原子类,他用到了位运算 一个 int 数值是 32 个 bit 位,这里采用高 3 位来保存运行状态,低 29 位来保存线程数量。

  •     当前池中线程比核心数少,新建一个线程执行任务
  •     核心池已满,但任务队列未满,添加到队列中
  •     核心池已满,队列已满,试着创建一个临时线程, 如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        int c = ctl.get();
      //1.当前池中线程比核心数少,新建一个线程执行任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

       //2.核心池已满,但任务队列未满,添加到队列中
        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);
        }
//3.核心池已满,队列已满,试着创建一个新线程
        else if (!addWorker(command, false))
//如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
            reject(command);
    }

   1.1 addWorker()创建核心线程或临时作线程方法做的事情: 

             (1)判断线程池状态

 if (rs >= SHUTDOWN &&! (rs == SHUTDOWN && firstTask == null &&! workQueue.isEmpty()))
               return false;

            (2) 创建线程之前,要判断是否超过设定的阈值.   如果是创建核心线程比较与corePoolSize 的大小,如果是创建临时线程比较与maximumPoolSize

if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))
     return false;

           (3)用用循环 CAS 操作来将线程数加 1  ,失败则重试(go to语句)

if (compareAndIncrementWorkerCount(c))
                    break retry;

           (4)新建一个线程(创建过程加锁)并启动。 

 boolean workerStarted = false; //工作线程是否启动的标识
 boolean workerAdded = false; //工作线程是否已经添加成功的标识
 Worker w = null; 
 try {
 //构建一个 Worker,这个 worker 是什么呢?我们可以看到构造方法里面传入了一个 Runnable 对象
 w = new Worker(firstTask);
 final Thread t = w.thread; //从 worker 对象中取出线程
 if (t != null) {
 final ReentrantLock mainLock = this.mainLock;
//这里有个重入锁,避免并发问题
 mainLock.lock(); 
 try {
 int rs = runStateOf(ctl.get());

 //只有当前线程池是正在运行状态,[或是 SHUTDOWN 且 firstTask 为空],才能添加到 workers 集合中
 if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {

//任务刚封装到 work 里面,还没 start,你封装的线程就是 alive,几个意思?肯定是要抛异常出去的
 if (t.isAlive()) 
  throw new IllegalThreadStateException();

 workers.add(w); //将新创建的 Worker 添加到 workers 集合中
 
int s = workers.size();
//如果集合中的工作线程数大于最大线程数,这个最大线程数表示线程池曾经出现过的最大线程数
 if (s > largestPoolSize) 
 largestPoolSize = s; //更新线程池出现过的最大线程数
 workerAdded = true;//表示工作线程创建成功了
 }
 } finally {
 mainLock.unlock(); //释放锁 }
 if (workerAdded) {//如果 worker 添加成功
 t.start();//启动线程
 workerStarted = true;
 }
 }
 } finally {
 if (! workerStarted)
 addWorkerFailed(w); //如果添加失败,回滚,就是递减实际工作线程数
 }
 return workerStarted;//返回结果
}

          1.1.1 new worker(firstTask):新建工作线程

     worker 实现了Runnable 接口在worker的构造函数中  getThreadFactory().newThread(this);  其中this就是woker对象,所以  t.start() 等价于new Thread (new worker).start(),可以启动线程,然后执行run方法

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
  final Thread thread; //这才是真正执行 task 的线程,从构造函数可知是由ThreadFactury 创建的

 Runnable firstTask; //这就是需要执行的 task

 volatile long completedTasks; //完成的任务数,用于线程池统计

 Worker(Runnable firstTask) {
 
setState(-1); //初始状态 -1,防止在调用 runWorker(),也就是真正执行 task前中断 thread。

 this.firstTask = firstTask;
 this.thread = getThreadFactory().newThread(this);
 }
  public void run() {runWorker(this);}
}

            1.1.2 runWorker(this):  运行工作线程

  •             如果 task 不为空,则开始执行 task,如果 task 为空,则通过 getTask()再去取任务,并赋值给 task,如果取到的 Runnable 不为空,执行该任务
  •             执行完毕后,通过 while 循环继续 getTask()取任务 。在这里实现了 [线程复用] 
  •             如果 getTask()取到的任务依然是空,那么整个 runWorker()方法执行完毕
final void runWorker(Worker w) {
 Thread wt = Thread.currentThread();
 Runnable task = w.firstTask;
 w.firstTask = null;

//unlock,表示当前 worker 线程允许中断,因为 new Worker 默认的 state=-1,此处是调用Worker 类的tryRelease()方法,将 state 置为 0,而 interruptIfStarted()中只有 state>=0 才允许调用中断
 w.unlock();

 boolean completedAbruptly = true;
 try {
//注意这个 while 循环,在这里实现了 [线程复用]  如果 task 为空,则通过getTask 来获取任务
 while (task != null || (task = getTask()) != null) {

//上锁,不是为了防止并发执行任务,为了在 shutdown()时不终止正在运行的 worker
 w.lock(); 

//线程池为 stop 状态时不接受新任务,不执行已经加入任务队列的任务,还中断正在执行的任务所以对于 stop 状态以上是要中断线程的
//(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP)确保线程中断标志位为 true 且是 stop 状态以上,接着清除了中断标志
//!wt.isInterrupted()则再一次检查保证线程需要设置中断标志位
 
if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&
 runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
 wt.interrupt();

 try {

//这里默认是没有实现的,在一些特定的场景中我们可以自己继承 ThreadpoolExecutor 自己重写
 beforeExecute(wt, task);
 Throwable thrown = null;
 try {
 task.run(); //执行任务中的 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,需要再通过 getTask()取) + 记录该 Worker 完成任务数量 + 解锁
 task = null;
 w.completedTasks++;
 w.unlock();
 }
 }
 completedAbruptly = false;
 } finally {
 processWorkerExit(w, completedAbruptly);
//1.将入参 worker 从数组 workers 里删除掉;
//2.根据布尔值 allowCoreThreadTimeOut 来决定是否补充新的 Worker 进数组workers
 }
}

               1.1.2.1 geTask()

  •        判断线程池状态,对于shutdown且 workQueue为空或stop的workerCount-1,并且返回 null
  •       比较前线程池中的线程数量大于核心线程数量。对于对于超过核心线程数量的这些线程(临时线程),需要进行超时控制。(ps: allowCoreThreadTimeOut 默认为false,核心线程默认不允许进行超时)
  •        超时控制就是在线程从工作队列 poll 任务时,加上了超时限制,如果线程在 keepAliveTime 的时间内 poll 不到任务,那我就认为这条线程没事做, 可以干掉了。
  •        如果拿到的任务不为空,则直接返回给 worker 进行处理
private Runnable getTask() {
 boolean timedOut = false; // Did the last poll() time out?
 for (;;) {
 int c = ctl.get(); 
int rs = runStateOf(c);

 // 对线程池状态的判断,两种情况会 workerCount-1,并且返回 null
 // 线程池状态为 shutdown,且 workQueue为空(shutdown 状态的线程池还是要执行workQueue 中剩余的任务的)
 /线程池状态为stop(shutdownNow()会导致变成 STOP)(此时不用考虑 workQueue的情况)

 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    decrementWorkerCount();
     return null;//返回 null,则当前 worker 线程会退出
 }

 int wc = workerCountOf(c);

 // timed 变量用于判断是否需要进行超时控制。
// allowCoreThreadTimeOut 默认是 false,也就是核心线程不允许进行超时;
 // wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;对于超过核心线程数量的这些线程,需要进行超时控制
 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

//1. 线程数量超过 maximumPoolSize 可能是线程池在运行时被调用了 setMaximumPoolSize()被改变了大小,否则已经 addWorker()成功不会超过 maximumPoolSize
//2. timed && timedOut 如果为 true,表示当前操作需要进行超时控制,并且上次从阻塞队列中获取任务发生了超时.其实就是体现了空闲线程的存活时间

 if ((wc > maximumPoolSize || (timed && timedOut))
 && (wc > 1 || workQueue.isEmpty())) {
 if (compareAndDecrementWorkerCount(c))
 return null;
 continue;
 }

 try {
根据 timed 来判断,如果为 true,则通过阻塞队列 poll 方法进行超时控制,如果在keepaliveTime 时间内没有获取到任务,则返回 null.否则通过 take 方法阻塞式获取队列中的任务
 
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
 workQueue.take();

 if (r != null)//如果拿到的任务不为空,则直接返回给 worker 进行处理
 return r;
timedOut = true;//如果 r==null,说明已经超时了,设置 timedOut=true,在下次自旋的时候进行回收
 } catch (InterruptedException retry) {
// 如果获取任务时当前线程发生了中断,则设置 timedOut 为false 并返回循环重试
 timedOut = false;
 }
 }
}

         1.1.3 processWorkerExit :线程销毁

               getTask 方法返回 null 时,在 runWorker 方法中会跳出 while 循环在 finally 中会调用 processWorkerExit,来销毁工作线 程。

   1.2 线程启动失败,回滚: addWorkerFailed(w);

  •        如果 worker 已经构造好了,则从 workers 集合中移除这个 worker      
  •        原子递减核心线程数(因为在 addWorker 方法中先做了原子增加)
  •       尝试结束线程池

 2.reject拒绝策略

AbortPolicy直接抛出异常,默认策略
CallerRunsPolicy用调用者所在的线程来执行任务
DiscardOldestPolicy丢弃阻塞队列中靠最前的任务,并执行当前任务
DiscardPolicy直接丢弃任务;

 当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值