线程池相关,这里记录 shutdown
、shutdownNow
和 awaitTermination
的功能用法。
shutdown
看看源码中对它的描述:
/**
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
* Invocation has no additional effect if already shut down.
*
* This method does not wait for previously submitted tasks to
* complete execution. Use {@link #awaitTermination awaitTermination}
* to do that.
*
* @throws SecurityException {@inheritDoc}
*/
主要看第一段,大意是:
- 在
shutdown
调用之前的任务会被执行下去 - 不会再接受新的任务
- 如果已经
shutdown
了,再调用不会有其他影响
写代码实践一下,提交10个任务,在第6个任务的时候执行 shutdown
:
static void shutdownTest() {
int count = 10;
ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(count));
for (int i = 0; i < count; i++) {
//try {
tp.execute(new Task(i));
//}
// catch (RejectedExecutionException e) {
// System.out.println("rejected, task-" + i);
// }
if (i == 5) {
tp.shutdown();
}
}
try {
tp.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------");
System.out.println("all tests finished");
}
static class Task implements Runnable {
String name = "";
public Task(int i) {
name = "task-" + i;
}
public String getName() {
return name;
}
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("sleep completed, " + getName());
} catch (InterruptedException e) {
System.out.println("interrupted, " + getName());
}
System.out.println(getName() + " finished");
}
}
public static void main(String[] args) {
shutdownTest();
}
运行结果如下,发现报出了 RejectedExecutionException
,说明任务被拒绝了:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task a_playground.thread.ThreadPoolTest$Task@12a3a380 rejected from java.util.concurrent.ThreadPoolExecutor@29453f44[Shutting down, pool size = 1, active threads = 1, queued tasks = 5, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at a_playground.thread.ThreadPoolTest.shutdownTest(ThreadPoolTest.java:18)
at a_playground.thread.ThreadPoolTest.main(ThreadPoolTest.java:69)
sleep completed, task-0
task-0 finished
sleep completed, task-1
task-1 finished
sleep completed, task-2
task-2 finished
sleep completed, task-3
task-3 finished
sleep completed, task-4
task-4 finished
sleep completed, task-5
task-5 finished
Process finished with exit code 1
把上面程序的注释去掉,catch 一下这个RejectedExecutionException
,运行结果如下:
rejected, task-6
rejected, task-7
rejected, task-8
rejected, task-9
sleep completed, task-0
task-0 finished
sleep completed, task-1
task-1 finished
sleep completed, task-2
task-2 finished
sleep completed, task-3
task-3 finished
sleep completed, task-4
task-4 finished
sleep completed, task-5
task-5 finished
-----------------
all tests finished
Process finished with exit code 0
程序运行到了最后,可以看到,第6个任务之后的任务都被拒绝了,其他任务正常执行。
所以 shutdown
方法将线程池状态置为 SHUTDOWN
,线程池并不会立即停止,要等正在执行和队列里等待的任务执行完才会停止。
shutdownNow
看看源码中对它的描述:
/**
* Attempts to stop all actively executing tasks, halts the
* processing of waiting tasks, and returns a list of the tasks
* that were awaiting execution. These tasks are drained (removed)
* from the task queue upon return from this method.
*
* This method does not wait for actively executing tasks to
* terminate. Use {@link #awaitTermination awaitTermination} to
* do that.
*
*
There are no guarantees beyond best-effort attempts to stop
* processing actively executing tasks. This implementation
* cancels tasks via {@link Thread#interrupt}, so any task that
* fails to respond to interrupts may never terminate.
*
* @throws SecurityException {@inheritDoc}
*/
第一段大意如下:
- 尝试停止所有正在执行的任务
- 停止等待任务的处理,并返回等待任务的列表
- 该方法返回时,这些等待的任务将从队列中清空
用代码实践一下,还是之前的代码,把 shutdown
改为 shutdownNow
,并打印一下返回的等待任务:
static void shutdownNowTest() {
int count = 10;
ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(count));
for (int i = 0; i < count; i++) {
try {
tp.execute(new Task(i));
} catch (RejectedExecutionException e) {
System.out.println("rejected, task-" + i);
}
if (i == 5) {
List tasks = tp.shutdownNow();
for (Runnable task : tasks) {
if (task instanceof Task) {
System.out.println("waiting task: " + ((Task) task).getName());
}
}
}
}
try {
tp.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------");
System.out.println("all tests finished");
}
运行结果如下:
interrupted, task-0
task-0 finished
waiting task: task-1
waiting task: task-2
waiting task: task-3
waiting task: task-4
waiting task: task-5
rejected, task-6
rejected, task-7
rejected, task-8
rejected, task-9
-----------------
all tests finished
Process finished with exit code 0
可以看到调用 shutdownNow
后,第一个任务0正在睡眠的时候,触发了 interrupt
中断,之前等待的任务1-5被从队列中清除并返回,之后的任务被拒绝。
这时回到对 shutdownNow
描述的第三段:
* There are no guarantees beyond best-effort attempts to stop
* processing actively executing tasks. This implementation
* cancels tasks via {@link Thread#interrupt}, so any task that
* fails to respond to interrupts may never terminate.
*
大意是,该方法是通过 interrupt
方法去终止正在运行的任务的,因此无法响应 interrupt
中断的任务可能不会被终止。所以,该方法是无法保证一定能终止任务的。
所以 shutdownNow
方法将线程池状态置为 STOP
,试图让线程池立刻停止,但不一定能保证立即停止,要等所有正在执行的任务(不能被 interrupt
中断的任务)执行完才能停止。
awaitTermination
shutdown
和 shutdownNow
的方法描述的第二段分别是这样的:
shutdown:不会等已提交的任务(在等待队列中的任务)完成执行,让 awaitTermination
来实现这个功能
* This method does not wait for previously submitted tasks to
* complete execution. Use {@link #awaitTermination awaitTermination}
* to do that.
shutdownNow:不会等已执行的任务的完成执行,让 awaitTermination
来实现这个功能
* This method does not wait for actively executing tasks to
* terminate. Use {@link #awaitTermination awaitTermination} to
* do that.
awaitTermination
的功能如下:
- 阻塞当前线程,等已提交和已执行的任务都执行完,解除阻塞
- 当等待超过设置的时间,检查线程池是否停止,如果停止返回
true
,否则返回false
,并解除阻塞
我们在上文中就使用了 awaitTermination
:
try {
tp.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------");
System.out.println("all tests finished");
任务没执行完,且没到设置时间,是不会执行下面两行打印代码的,现在把等待时间设置为1秒:
try {
boolean isStop = tp.awaitTermination(1, TimeUnit.SECONDS);
System.out.println("is pool finished: " + isStop);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------");
System.out.println("all tests finished");
执行第一个 shutdown
的例子,结果如下:
rejected, task-6
rejected, task-7
rejected, task-8
rejected, task-9
is pool finished: false
-----------------
all tests finished
sleep completed, task-0
task-0 finished
sleep completed, task-1
task-1 finished
sleep completed, task-2
task-2 finished
sleep completed, task-3
task-3 finished
sleep completed, task-4
task-4 finished
sleep completed, task-5
task-5 finished
Process finished with exit code 0
可以看到,达到设置时间后,就不再阻塞当前线程了,直接打印了下面两行代码,并且返回了 false
说明线程池没有停止。
有时我们需要主线程等所有子线程执行完毕后再运行,在所有任务提交后,调用shutdown
触发 awaitTermination
,阻塞主线程,当所有子线程执行完毕后,解除阻塞。
要注意的是,这个方法单独使用是无法停止线程池的,在如下代码中,注释了对 shutdown
的调用:
static void shutdownTest() {
int count = 10;
ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(count));
for (int i = 0; i < count; i++) {
try {
tp.execute(new Task(i));
} catch (RejectedExecutionException e) {
System.out.println("rejected, task-" + i);
}
// if (i == 5) {
// tp.shutdown();
// }
}
try {
boolean isStop = tp.awaitTermination(1, TimeUnit.SECONDS);
System.out.println("is pool finished: " + isStop);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------");
System.out.println("all tests finished");
}
结果如下:
is pool finished: false
-----------------
all tests finished
sleep completed, task-0
task-0 finished
sleep completed, task-1
task-1 finished
sleep completed, task-2
task-2 finished
sleep completed, task-3
task-3 finished
sleep completed, task-4
task-4 finished
sleep completed, task-5
task-5 finished
sleep completed, task-6
task-6 finished
sleep completed, task-7
task-7 finished
sleep completed, task-8
task-8 finished
sleep completed, task-9
task-9 finished
任务都执行完了,但程序并没有终止,线程池没有被停止(没有出现 Process finished with exit code 0
的提示)。所以awaitTermination
要和 shutdown
结合使用。
需要注意的是,awaitTermination
和 shutdown
执行时都会申请锁,awaitTermination
需要在 shutdown
调用后调用,awaitTermination
会在代码中不断检查线程池是否停止(这需要调用 shutdown
后等任务全部执行完毕),如果停止,则返回true并释放锁。
awaitTermination代码:
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (;;) {
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
if (nanos <= 0)
return false;
nanos = termination.awaitNanos(nanos);
}
} finally {
mainLock.unlock();
}
}
如果 shutdown
在 awaitTermination
后调用的话,在awaitTermination
未超时前,它不会释放锁;而 shutdown
也无法得到锁去让线程池停止。这就形成了死锁。