线程池提供了两个关闭方法,shutdownNow
和 shutdown
方法。
shutdownNow
方法的解释是:线程池拒接收新提交的任务,同时立马关闭线程池,线程池里的任务不再执行。
shutdown
方法的解释是:线程池拒接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池。
以上的说法虽然没错,但是还有很多的细节,比如调用shutdown
方法后,正在执行任务的线程做出什么反应?正在等待任务的线程又做出什么反应?线程在什么情况下才会彻底退出。如果不了解这些细节,在关闭线程池时就难免遇到,像线程池关闭不了,关闭线程池出现报错等情况。
再说这些关闭线程池细节之前,需要强调一点的是,调用完 shutdownNow
和 shuwdown
方法后,并不代表线程池已经完成关闭操作,它只是异步的通知线程池进行关闭处理。如果要同步等待线程池彻底关闭后才继续往下执行,需要调用awaitTermination
方法进行同步等待。
以下源码取自 jdk1.8。
shutdownNow
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP); //1
interruptWorkers(); //2
tasks = drainQueue(); //3
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
复制代码
在shutdownNow
方法里,重要的三句代码我用红色数字标出来了。
第一句就是原子性的修改线程池的状态为 STOP 状态
第三句是将队列里还没有执行的任务放到列表里,返回给调用方。
第二句是遍历线程池里的所有工作线程,然后调用线程的 interrupt 方法。如下:
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
复制代码
以上就是shutdownNow
方法的执行逻辑:将线程池状态修改为 STOP,然后调用线程池里的所有线程的 interrupt 方法。
调用shutdownNow
后,线程池里的线程会做如何反应呢?那就要看,线程池里线程正在执行的代码逻辑了。其在线程池的 runWorker 方法里,其代码如下:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.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;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
复制代码
正常情况下,线程池里的线程,就是在这个 while 循环里不停地执行。其中代码 task.run()就是在执行我们提交给线程池的任务,如当我们调用 shutdownNow 时,task.run()里面正处于 IO 阻塞,则会导致报错,如果 task.run()里正在正常执行,则不受影响,继续执行完这个任务。
从上面代码可以看出,如果 getTask()方法返回 null,也会导致线程的退出。我们再来看看 getTask 方法的实现:
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
复制代码
如果我们调用shutdownNow
方法时,线程处于从队列里读取任务而阻塞中,则会导致抛出 InterruptedException 异常,但因为异常被捕获,线程将会继续在这个 for 循环里执行。
还记得shutdownNow
方法里第一步将线程修改为 STOP 状态吧,当执行到上边if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty()))
判断时,由于 STOP 状态值是大于 SHUTDOWN 状态,STOP 也大于等于 STOP,不管任务队列是否为空,都会进入 if 语句从而返回 null,线程退出。
总结:当我们调用线程池的 shutdownNow 时,如果线程正在 getTask 方法中执行,则会通过 for 循环进入到 if 语句,于是 getTask 返回 null,从而线程退出。不管线程池里是否有未完成的任务。
如果线程因为执行提交到线程池里的任务而处于阻塞状态,则会导致报错(如果任务里没有捕获 InterruptedException 异常),否则线程会执行完当前任务,然后通过 getTask 方法返回为 null 来退出
shutdown
再来看下shutdown
方法的源码。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
复制代码
跟shutdownNow
类似,只不过它是将线程池的状态修改为 SHUTDOWN 状态,然后调用 interruptIdleWorkers 方法,来中断空闲的线程。这是 interruptIdleWorkers 方法的实现:
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) { // 注意这里有个w.tryLock()的判断
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
复制代码
跟shutdownNow
方法调用 interruptWorkers 方法不同的是,interruptIdleWorkers 方法在遍历线程池里的线程时,有一个 w.tryLock()加锁判断,只有加锁成功的线程才会被调用 interrupt 方法。那什么情况下才能被加锁成功?什么情况下不能被加锁成功呢?这就需要我们继续回到线程执行的 runWorker 方法。
在上边 runWorker 方法代码中,请注意 w.lock()和 w.unlock()调用。其实就是正运行在 w.lock 和 w.unlock 之间的线程将因为加锁失败,而不会被调用 interrupt 方法,换句话说,就是正在执行线程池里任务的线程不会被中断。
不管是被调用了 interrupt 的线程还是没被调用的线程,什么时候退出呢?,这就要看 getTask 方法的返回是否为 null 了。
在 getTask 里的 if 判断(上文中 getTask 代码截图中上边红色方框的代码)中,由于线程池被 shutdown 方法修改为 SHUTDOWN 状态,SHUTDOWN 大于等于 SHUTDOWN 成立没问题,但是 SHUTDOWN 不再大于等于 STOP 状态,所以只有队列为空,getTask 方法才会返回 null,导致线程退出。
总结:当我们调用线程池的 shuwdown 方法时,如果线程正在执行线程池里的任务,即便任务处于阻塞状态,线程也不会被中断,而是继续执行。
如果线程池阻塞等待从队列里读取任务,则会被唤醒,但是会继续判断队列是否为空,如果不为空会继续从队列里读取任务,为空则线程退出。
总结
当我们调用 shutdownNow
或者 shutdown
方法去尝试关闭 java 线程池,都只是异步的通知线程池,此时线程池不会立即停止。如果要同步等待线程池彻底关闭后才继续往下执行,需要调用awaitTermination
方法进行同步等待。
评论