正确处理多线程中的 InterruptedException
2024-03-28 10:32:53 # Technical # JavaBase

通常在处理异常时总是一 catch Exception 了之,但如果含有 InterruptedException 时,idea 会提示要处理 InterruptedException。是什么原因要特殊处理这个异常,没怎么仔细了解过

认识下 InterruptedException

直接打开 InterruptedException 的源码

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
/**
* Thrown when a thread is waiting, sleeping, or otherwise occupied,
* and the thread is interrupted, either before or during the activity.
* Occasionally a method may wish to test whether the current
* thread has been interrupted, and if so, to immediately throw
* this exception. The following code can be used to achieve
* this effect:
* {@snippet lang=java :
* if (Thread.interrupted()) // Clears interrupted status!
* throw new InterruptedException();
* }
*
* @author Frank Yellin
* @see java.lang.Object#wait()
* @see java.lang.Object#wait(long)
* @see java.lang.Object#wait(long, int)
* @see java.lang.Thread#sleep(long)
* @see java.lang.Thread#interrupt()
* @see java.lang.Thread#interrupted()
* @since 1.0
*/
public class InterruptedException extends Exception {
@java.io.Serial
private static final long serialVersionUID = 6700697376100628473L;

/**
* Constructs an {@code InterruptedException} with no detail message.
*/
public InterruptedException() {
super();
}

/**
* Constructs an {@code InterruptedException} with the
* specified detail message.
*
* @param s the detail message.
*/
public InterruptedException(String s) {
super(s);
}
}

直接看最上方的 javadoc 可以了解到

当线程处于 waitsleep 以及 其他方式被占用 或者 处于活动期间 时,如果这个线程被中断则会抛出 InterruptedException。有时,一个方法希望测试当前线程是否已经被中断,如果是,则立即抛出此异常。可以使用下面的代码来实现这个效果:

1
2
3
if (Thread.interrupted()) {
throw new InterruptedException();
}

简单理解起来就是,处于等待(挂起,没有执行逻辑)中的线程,如果被执行中断,就会立马抛出 InterruptedException

中断线程等待

终止一个等待中或者休眠中的线程,会使它们立即醒来,并抛出 InterruptedException 异常,但它们并不会停止工作

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
public class InterruptingTest {

public static void main(String[] args) throws InterruptedException {
MyClass myClass = new MyClass();
myClass.start();

TimeUnit.SECONDS.sleep(1);

myClass.interrupt();

System.out.println("Main thread over...");
}
}

class MyClass extends Thread {

@Override
public void run() {
try {
System.out.println("Child thread run...");
System.out.println("Child thread is interrupted: " + Thread.currentThread().isInterrupted());
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
System.out.println("catch interrupt exception");
}
}
}

Output

1
2
3
4
Child thread run...
Child thread is interrupted: false
Main thread over...
catch interrupt exception

myClass.interrupt(); 的调用,使 sleep 中的线程立即结束了 sleep,并抛出了 InterruptedException

拒绝线程等待

与上面不同,如果一个线程在等待或者休眠前,就已经被中断了,那么该线程就会在进入等待之处或者还未进入等待时,就会抛出异常

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
public class InterruptingTest {

public static void main(String[] args) throws InterruptedException {
MyClass myClass = new MyClass();
myClass.start();

// TimeUnit.SECONDS.sleep(1);

myClass.interrupt();

System.out.println("Main thread over...");
}
}

class MyClass extends Thread {

@Override
public void run() {
try {
System.out.println("Child thread run...");
System.out.println("Child thread is interrupted: " + Thread.currentThread().isInterrupted());
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
System.out.println("catch interrupt exception");
}
}
}

Output

1
2
3
4
Main thread over...
Child thread run...
Child thread is interrupted: true
catch interrupt exception

与中断线程等待相比,注释掉第 7 行主线程的等待,子线程在执行 sleep 前,就已经被中断,所以在执行 sleep 时,立即抛出了 InterruptedException

中断标记与清除

将上面的示例再改变一下:

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
public class InterruptingTest {

public static void main(String[] args) throws InterruptedException {
MyClass myClass = new MyClass();
myClass.start();

// TimeUnit.SECONDS.sleep(1);

myClass.interrupt();

System.out.println("Main thread over...");
}
}

class MyClass extends Thread {

@Override
public void run() {
try {
System.out.println("Child thread run...");
System.out.println("Child thread is interrupted: " + Thread.currentThread().isInterrupted());
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
System.out.println("catch interrupt exception");
}
System.out.println("Child thread is interrupted: " + Thread.currentThread().isInterrupted());
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}

Output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Main thread over...
Child thread run...
Child thread is interrupted: true
catch interrupt exception
Child thread is interrupted: false
0
1
2
3
4
5
6
7
8
9

还是拒绝等待,在抛出 InterruptedException 异常后,可以发现线程依然是可以正常执行的,而且线程的中断状态被清除了,那么后面的等待与休眠是不被影响

如何处理 InterruptedException

回到开头,发生 InterruptedException 异常后,之所以提示需要特殊处理 InterruptedException 是因为抛出 InterruptedException 意味着当前线程被标记中断需要尽快释放资源并告知调用者,如果不 catch 并处理,后续的等待或休眠操作将继续无意义地占用资源

正常处理方式应该是 catch InterruptedException 并设置中断标志或者向上抛出

维护中断标志

1
2
3
4
5
6
catch (InterruptedException e) {
// 重新设置中断状态
Thread.currentThread().interrupt();
// 处理 InterruptedException
// ...
}

向上抛出

1
2
3
4
5
// 方法签名声明可能抛出 InterruptedException
public void myMethod() throws InterruptedException {
// 可能抛出 InterruptedException 的代码块
// ...
}