Java 中断机制

#中断

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:虽然 InputStreamOutputStream 中的 read()write() 方法不会响应中断,但通过关闭底层的套接字,可以使得由于执行 read()write() 等方法而被阻塞的线程抛出一个 SocketException
  • Java.io 中的同步 I/O:当中断一个正在 InterruptibleChannel 上等待的线程时,将抛出 ClosedByInterruptException 并关闭链路(在此链路上等待的其他线程也都抛出该异常)。当关闭一个 InterruptibleChannel 时,将导致所有在链路操作上阻塞的线程抛出 AsynchronousCloseException
updatedupdated2022-08-262022-08-26