线程池系列 - (5)shutdown && shutdownNow

769

系列文章

shutdown && shutdownNow

线程池的状态一节中。知道了线程池的5种状态。如👇图所示

也知道了其转换过程 如👇 所示

具体来看一下,这两个的区别以及实现原理

区别

对比shutdownshutdownNow
返回值voidList<Runnable>
相同点阻止新来的任务提交阻止新来的任务提交
不同对已经提交了的任务不会产生任何影响会中断当前正在运行的线程

shutdown

分析一下shutdown源码。看一下他是如何执行的。依然跟着流程图去分析。看一下线程池是如何通过shutdown改变其状态的。

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    // 上锁,确保同一时间只能有一个线程执行此操作
    mainLock.lock();
    try {
        // 检查方法调用方是否用权限关闭线程池以及中断工作线程
        checkShutdownAccess();
        // 将线程池运行状态设置为SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 中断所有空闲线程
        interruptIdleWorkers();
        // 此方法在ThreadPoolExecutor中是空实现,具体实现在其子类ScheduledThreadPoolExecutor
        // 中,用于取消延时任务。
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    // 尝试将线程池置为TERMINATED状态
    tryTerminate();
}

上面我们看到其主要就分成以下几个部分

  • 上锁
  • checkShutdownAccess
  • advanceRunState(SHUTDOWN)
  • interruptIdleWorkers()
  • onShutdown()
  • 解锁
  • tryTerminate

抛弃上锁解锁。分析其重要的几个方法。作用以及实现。

advanceRunState 将线程池运行状态设置为SHUTDOWN

重要的是在线程池的状态一节说过。

线程池通过 ctlOf方法更新线程池状态和线程数量。这里就是通过此方法将线程池的状态 从 Running 改成 SHUTDOWN

private void advanceRunState(int targetState) {
    // 程序循环执行,直到将运行状态设为目标值
    for (;;) {
        // 获取AtomicInteger变量中的整数值
        int c = ctl.get();
        // if条件的前半部分是判断当前运行状态是否大于等于给定值
        if (runStateAtLeast(c, targetState) ||
            // 后半部分利用CAS操作将运行状态设置为目标值,成功的话会返回true,失败则返回false
            // 👇 核心方法
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
     }
 }

interruptIdleWorkers 中断所有空闲线程

// 下面第二个方法的重载,将参数设为false,也就是中断所有空闲线程
private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

重点在于这个入参 onlyOne 字面意思

  • false,中断所有空闲线程
  • true,则只中断一个空闲线程

通过源码可以知道其中断线程使用的是线程的interrupt()方法。

对于线程不不理解的小伙伴可以参考笔者的另一个文章线程总结.对线程有很详细的说明

// 中断空闲线程,如果参数为false,中断所有空闲线程,如果为true,则只中断一个空闲线程
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            // 如果线程还未中断且处于空闲状态,将其中断。if条件的前半部分很好理解,后半部分 w.tryLock() 方法  
            // 调用了tryAcquire(int)方法 (继承自AQS),也就是尝试以独占的方式获取资源,成功则返回true,表示            
            // 线程处于空闲状态;失败会返回false,表示线程处于工作状态。
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            // 如果为true,表示只中断一个空闲线程,并退出循环,这一情况只会用在tryTerminate()方法中
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

在其中断线程的地方看一下去判断是否可以中断线程的判断逻辑。有两处

  • !t.isInterrupted()

首先要满足线程没有被打断的条件

  • w.tryLock()

重点看一下这个方法,此方法继承自AQS,也就是尝试以独占的方式获取资源,成功则返回true,失败则返回false

换句话说。如果一个任务已经执行了。那么就不会进入中断的逻辑。就保证了其对已经提交了的任务不会产生任何影响的作用

tryTerminate 尝试将线程池置为 TERMINATED 状态

这一步也是最后一步。就是想线程池的状态从 SHUTDOWN 改成 TERMINATED

来看一下其内部实现

final void tryTerminate() {
        for (;;) {
            // 获取ctl中的整型值
            int c = ctl.get();
            // 线程池处于RUNNING状态
            if (isRunning(c) ||
                // 线程池处于TIDYING状态或TERMINATED状态
                runStateAtLeast(c, TIDYING) ||
                // 线程池处于SHUTDOWN状态但是任务队列不为空
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            // 执行到这里说明 当前线程池的状态是 `STOP` 或者  `SHUTDOWN`  
            // 当线程池中的线程数量不是0 。就是说有工作的线程  
            if (workerCountOf(c) != 0) { // Eligible to terminate
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // 将线程池状态改成 `TIDYING`
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        // 空方法
                        terminated();
                    } finally {
                        // 将线程池状态改成 `TERMINATED`
                        ctl.set(ctlOf(TERMINATED, 0));
                        // 唤醒其他正在等待的线程
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

通过上面的源码分析。可以知道。使用的还是 ctlOf 去更改状态。

  • 先改成 TIDYING 状态
  • 在改成 TERMINATED 状态

shutdownNow

对比之前的 shutdown 是非常相似的。

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 检查方法调用方是否用权限关闭线程池以及中断工作线程
        checkShutdownAccess();
        // 将线程池运行状态置为STOP
        advanceRunState(STOP);
        // 中断所有线程,包括正在运行的线程
        interruptWorkers();
        // 将未执行的任务移入列表中
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    // 尝试将线程池置为TERMINATED状态
    tryTerminate();
    return tasks;
}


interruptWorkers 中断所有线程,包括正在运行的线程

对比到shutdown来说,他更加直接。直接中断所有线程。不存在判断是否线程已经执行了。

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 中断所有线程
        for (Worker w : workers)
            // 调用内部类Worker自身方法中断线程
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

通过源码能够知道他中断线程的方式和 shutdown 是一样的。都是使用 interrupt

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

drainQueue 将未执行的任务移入列表中

此方法是将队列中的任务保存到数组中。

private List<Runnable> drainQueue() {
    //将队列中的 Runnable 转移到  taskList
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    //移除此队列中所有可用的元素,并将它们添加到给定 collection 中
    q.drainTo(taskList);
    if (!q.isEmpty()) {
        for (Runnable r : q.toArray(new Runnable[0])) {
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}