线程中断机制提供了一种方法,用于将线程从阻塞等待中唤醒,尝试打断目标线程的现有处理流程,使之响应新的命令。Java 留给开发者这一自由,我们应当予以善用。
今天我们聊聊 Java 线程的中断机制。
线程中断机制提供了一种方法,有两种常见用途:
将线程从阻塞等待中唤醒,并作出相应的“受控中断”处理。
尝试告知目标线程:请打断现有处理流程,响应新的命令。
以第一种用途为例,请看以下代码:
1
2
3
4
5
6
7
8
9
|
synchronized (lock) { try { while (!check()) { lock.wait(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } |
这段代码使用了 Java 提供的 wait/notify 机制,线程执行 lock.wait() 会阻塞,有三种情况使线程恢复运行。
1、超时 1000ms 结束,正常执行下一句代码。
2、另一个线程执行下述代码主动唤醒
1
2
3
|
synchronized (lock) { lock.notifyAll(); // or lock.notify(); } |
这也会正常执行下一句代码。
3、另一个线程要求等待的线程“中断”
1
2
3
|
// 拿到等待中的线程的引用 Thread a; a.interrupt(); |
被“中断”的线程 a,会在 lock.wait() 处抛出 InterruptedException 异常。
综上所述,你可以认为 object.wait() 内部在做这些事:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
boolean checkTimeout = timeout > 0; Thread current = Thread.currentThread(); lock.addWaiter(current); while (!current.isNotified()) { if (current.isInterrupted()) { current.clearInterrupted(); throw new InterruptedException(); } if (checkTimeout) { if (timeout == 0) break; timeout--; } } |
这不完全准确,因为 wait 不使用这种“忙轮询”的方式做检查,但关于标志位的判断逻辑是正确的。
让我们从上文所述的“手动发出中断”这一操作开始探究
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// sun.nio.ch.Interruptible public interface Interruptible { void interrupt(Thread var1); } // java.lang.Thread private volatile Interruptible blocker; private final Object blockerLock = new Object(); public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); b.interrupt(this); return; } } interrupt0(); } // Just to set the interrupt flag private native void interrupt0(); |
能够看出,thread.interrupt() 先判断权限,然后实际调用 interrupt0() 设置线程的中断标志,如果当前线程有 nio 的 Interruptible 那么还会回调它。
注意,interrupt0() 只是设置了线程的中断标志。
当一个线程并不阻塞,没有在 object.wait(), thread.join(), Thread.sleep() 等不受 Java 程序逻辑控制的区域时,那么会发生什么事情?答案是不会发生任何事情,线程是否被打断只能通过主动地检查中断标志得知。
怎么检查?Thread 暴露了两个接口,Thread.interrupted() 和 thread.isInterrupted()。
1
2
3
4
5
6
7
8
|
// java.lang.Thread public static boolean interrupted() { return currentThread().isInterrupted(true); } public boolean isInterrupted() { return isInterrupted(false); } private native boolean isInterrupted(boolean clearInterrupted); |
能够看出,两者都是依靠内部的 isInterrupted(boolean),而它会返回线程是否被打断,并根据需要清空中断标志。
当一个函数调用会发生阻塞,Java 库函数在阻塞的源头签名里标记 throws InterruptedException,并要求编写 try catch 处理中断。
当线程发生了阻塞,就像上文所述,Java 检查到中断标志,先将其清除,然后抛出 InterruptedException。
1
2
3
4
5
|
// java.lang.Object public final void wait() throws InterruptedException { wait(0); } public final native void wait(long timeout) throws InterruptedException; |
如果一个线程收到 InterruptedException,之后仍然执行了会引发阻塞的代码,它将像“没事人”一样继续阻塞住。因为 Java 在内部将中断标志清除了!
我们常见地编写以下三类处理 InterruptedException 的代码:
将 InterruptedException 交由上层处理。
1
2
3
4
5
|
public void foo() throws InterruptedException { synchronized (lock) { lock.wait(); } } |
遇到 InterruptedException 重设中断标志位。
1
2
3
4
5
6
7
8
|
try { synchronized (lock) { lock.wait(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); //break; } |
先忙完,再重新抛出 InterruptedException。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public void bar() throws InterruptedException { InterruptedException ie = null; boolean done = false; while (!done) { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { ie = e; continue; } } done = true; } if (ie != null) { throw ie; } } |
如果一个线程无视中断标志和 InterruptedException,它仍然能够跑的很好。但这与我们设计多线程的初衷是违背的,我们希望线程之间是和谐的有序协作以实现特定功能,因此受控线程应当对中断作出响应。而 Java 留给开发者这一自由,我们应当予以善用。
以上就是这次给大家介绍的Java线程的中断机制相关知识的全部内容,如果还有任何不明白的可以在下方的留言区域讨论,感谢对服务器之家的支持。
原文链接:https://my.oschina.net/tridays/blog/1587259