Java 的中断是一种协作机制,能够使一个线程(包括被中断的线程本身)终止另一个线程的当前工作。以前使用 stop
方法暴力停止线程的方式会使共享的数据结构处于不一致的状态;而使用协作的方式,请求一个线程终止,被请求的线程会先清除正在执行的工作,然后再结束,因为任务本身的代码比发出请求的代码更清楚如何执行清除工作。
对中断的正确理解是:他并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由目标线程在下一个合适的时刻中断自己。
每一个线程都有一个 boolean
类型的中断状态,在 Thread
中有三个方法与之相关。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public class Thread implements Runnable {
// 请求中断线程,设置中断标志位
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
// 测试当前线程是否被中断。使用该方法会清除线程的中断状态!
// 换句话说,如果这个方法被连续调用两次,第二次调用将返回 false
// 除非当前线程再次被中断(在第一次调用清除其中断状态之后,第二次调用检查它之前)
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
// 返回当前线程的中断状态,不会改变中断状态。
public boolean isInterrupted() {
return isInterrupted(false);
}
// 上面两个方法都是通过调用该方法实现的
private native boolean isInterrupted(boolean ClearInterrupted);
}
|
Java 中断是协作机制,因此两个线程必须合作才能正确的中断,如果在目标线程中没有轮询中断状态,那么中断将会被忽略。
如果任务代码中调用了可中断阻塞,包括 Object 类里的 wait()
、wait(long)
、wait(long, int)
,Thread 类中的 join()
、join(long)
、join(long, int)
、sleep(long)
、sleep(long, int)
,这些方法都会检测线程的中断,并在检测到中断时提前返回。它们在响应中断时执行的操作包括:清除中断状态,抛出 InterruptedException
,表示阻塞操作由于中断而提前结束。
调用可中断阻塞时,处理捕获的 InterruptedException
的两种策略:
- 传递异常(可以在传递异常前进行其他操作),这样被中断线程也抛出
InterruptedException
异常,使方法也成为可中断的方法。
- 恢复中断状态(因为可中断阻塞抛出异常的时候会清除中断状态),即调用
Thread.currentThread.interrupt()
,这样在调用栈的上层代码中可以根据中断状态对它进行处理。
如果在任务代码中没有调用可中断阻塞,那么可以在任务代码中轮询当前线程的中断状态来响应中断。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
// 线程不会被中断,因为目标线程没有响应中断
public static void test1() {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("Running");
}
});
thread.start();
thread.interrupt();
}
// 中断成功,检测到中断后,线程输出 Interrupted 然后退出
// 中断不是立即退出,可以执行一些清理工作后再退出或者抛出异常
public static void test() {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("Running");
// 检查中断标志
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted");
return;
}
}
});
thread.start();
thread.interrupt();
}
// 中断失败,目标线程启动后休眠 3 秒,主线程在目标线程启动后 2 秒请求中断
// sleep 方法会抛出 InterruptedException 异常并清除中断状态
// 在代码中再去检查中断标志时为 false
// 正确方法应该在 catch 块中恢复中断状态
public static void test() throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted");
return;
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("Catch InterruptedException");
// 此处应该恢复中断状态
// Thread.currentThread.interrupt();
}
}
});
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
|
使用线程池的 ExecutorService.submit()
方法会返回一个 Future
来描述任务。Future
拥有一个 cancel()
方法,可以尝试取消任务的执行。
1
2
3
4
5
6
|
// 如果任务已经完成、已经取消、无法取消,则尝试失败
// 如果调用取消时任务还没有启动,那么任务就不应该执行
// 如果调用取消时,已经运行了,则根据参数 mayInterruptIfRunning 进行不同的操作
// 如果 mayInterruptIfRunning 为 true,则表示应该中断执行该任务的线程
// 如果 mayInterruptIfRunning 为 false,则允许该任务继续执行
boolean cancel(boolean mayInterruptIfRunning);
|
Java.io
中的同步 Socket I/O:虽然 InputStream
和 OutputStream
中的 read()
和 write()
方法不会响应中断,但通过关闭底层的套接字,可以使得由于执行 read()
和 write()
等方法而被阻塞的线程抛出一个 SocketException
。
Java.io
中的同步 I/O:当中断一个正在 InterruptibleChannel
上等待的线程时,将抛出 ClosedByInterruptException
并关闭链路(在此链路上等待的其他线程也都抛出该异常)。当关闭一个 InterruptibleChannel
时,将导致所有在链路操作上阻塞的线程抛出 AsynchronousCloseException
。