常见Java并发中断相关方法

常见Java并发中断方法

Posted by John Doe on 2020-11-26
Words 1.4k and Reading Time 5 Minutes
Viewed Times

背景

在极客时间学习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
@Deprecated
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());
}

@Deprecated
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);

  1. stop():
    虽然 stop() 方法确实可以停止一个正在运行的线程,但我们通过源码可以发现:该方法已被@Deprecated标识,表没该方法已被弃用,不推荐使用。
    弃用原因:
  • 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
  • 调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
    同理还有类似suspend()、resume()方法,本文就不过多介绍这类弃用方法了。
  1. interrupt():
    interrupt() 方法是 唯一一个 可以将上面提到中断标志设置为 true 的方法,从上面源码可以看出,这是一个 Thread 类 public的对象方法,所以可以推断出任何线程对象都可以调用该方法,进一步说明就是可以一个线程 interrupt 其他线程,也可以 interrupt自己。其中,中断标识的设置是通过 native 方法 interrupt0 完成的。
    interrupt0

同时,我们关注一下源码注释:
threadBlocked
表达为当线程被阻塞在:wait()、join()、sleep()这些方法时,如果被中断,就会抛出 InterruptedException 异常。

1
2
3
public final void wait() throws InterruptedException {
wait(0);
}

同理,这些抛出了InterruptedException异常的方法表明这些是可以中断的。
总结:当调用了interrupt()方法,线程的中断标志变为true。

1
interrupt0();           // Just to set the interrupt flag即只修改

  1. isInterrupted():
    该方法就是返回中断标识的结果:
  • true:线程被中断,
  • false:线程没被中断或被清空了中断标识(如何清空我们一会看)
    拿到这个标识后,线程就可以判断这个标识来执行后续的逻辑
  1. 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() 方法,检测是不是自己被中断了。

即抛出异常throws InterruptedException的方法实现线程中断,interrupt方法用于通知中断

This is copyright.

...

...

00:00
00:00