背景
在极客时间学习09Java线程的生命周期(上)时,课后思考出现了本文没有介绍的isInterrupted()方法,特此记录学习Java并发中断方法。
中断介绍
并发编程引用了中断机制无疑证明了中断对多线程并发的裨益,想想死锁的四个条件:互斥、请求与保持、环路等待、不可抢占。中断可以完美的破坏请求与保持、环路等待的死锁条件。再比如有的线程可能迷失在怪圈无法自拔(自旋浪费资源),这时就可以用其他线程在恰当的时机给它个中断通知,被“中断”的线程可以选择在恰当的时机选择跳出怪圈,最大化的利用资源。
了解Thread类中断方法之前我们先了解一下中断标识,Java 的每个线程对象里都有一个 boolean 类型的标识,代表是否有中断请求,注:这个标识通过底层 native 方法实现的。
中断相关方法
先上源码: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
public final void stop() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
checkAccess();
if (this != Thread.currentThread()) {
security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
}
// A zero status value corresponds to "NEW", it can't change to
// not-NEW because we hold the lock.
if (threadStatus != 0) {
resume(); // Wake up thread if it was suspended; no-op otherwise
}
// The VM can handle all thread states
stop0(new ThreadDeath());
}
public final synchronized void stop(Throwable obj) {
throw new UnsupportedOperationException();
}
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();
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
- stop():
虽然 stop() 方法确实可以停止一个正在运行的线程,但我们通过源码可以发现:该方法已被@Deprecated标识,表没该方法已被弃用,不推荐使用。
弃用原因:
- 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
- 调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
同理还有类似suspend()、resume()方法,本文就不过多介绍这类弃用方法了。
- interrupt():
interrupt() 方法是 唯一一个 可以将上面提到中断标志设置为 true 的方法,从上面源码可以看出,这是一个 Thread 类 public的对象方法,所以可以推断出任何线程对象都可以调用该方法,进一步说明就是可以一个线程 interrupt 其他线程,也可以 interrupt自己。其中,中断标识的设置是通过 native 方法 interrupt0 完成的。
同时,我们关注一下源码注释:
表达为当线程被阻塞在:wait()、join()、sleep()这些方法时,如果被中断,就会抛出 InterruptedException 异常。
1 | public final void wait() throws InterruptedException { |
同理,这些抛出了InterruptedException异常的方法表明这些是可以中断的。
总结:当调用了interrupt()方法,线程的中断标志变为true。1
interrupt0(); // Just to set the interrupt flag即只修改
- isInterrupted():
该方法就是返回中断标识的结果:
- true:线程被中断,
- false:线程没被中断或被清空了中断标识(如何清空我们一会看)
拿到这个标识后,线程就可以判断这个标识来执行后续的逻辑
- interrupted():
和上面的 isInterrupted() 方法差不多,两个方法都是调用 private 的 isInterrupted() 方法, 唯一差别就是会清空中断标识。常用于当处理线程要被大量中断并且只处理其中一次中断。
interrupt()方法理解注意:
interrupt() 方法仅仅是通知线程,线程有机会执行一些后续操作,同时也可以无视这个通知。被 interrupt 的线程,是怎么收到通知的呢?一种是异
常,另一种是主动检测。当线程 A 处于 WAITING、TIMED_WAITING 状态时,如果其他线程调用线程 A 的interrupt() 方法,会使线程 A 返回到 RUNNABLE 状态,同时线程 A 的代码会触发InterruptedException 异常。上面我们提到转换到 WAITING、TIMED_WAITING状态的触发条件,都是调用了类似wait()、join()、sleep() 这样的方法,我们看这些方法的签名,发现都会 throws InterruptedException 这个异常。这个异常的触发条件就是:其他线程调用了该线程的 interrupt() 方法。当线程 A 处于 RUNNABLE 状态时,并且阻塞在 java.nio.channels.InterruptibleChannel上时,如果其他线程调用线程 A 的 interrupt() 方法,线程 A 会触发java.nio.channels.ClosedByInterruptException 这个异常;而阻塞在java.nio.channels.Selector 上时,如果其他线程调用线程 A 的 interrupt() 方法,线程 A的 java.nio.channels.Selector 会立即返回。上面这两种情况属于被中断的线程通过异常的方式获得了通知。还有一种是主动检测,如果线程处于 RUNNABLE 状态,并且没有阻塞在某个 I/O 操作上,例如中断计算圆周率的线程 A,这时就得依赖线程 A 主动检测中断状态了。如果其他线程调用线程 A 的interrupt()方法,那么线程 A 可以通过 isInterrupted() 方法,检测是不是自己被中断了。
...
...
This is copyright.