對于很多剛接觸編程的人來說,對于線程中斷和線程阻塞兩個概念,經常性是混淆起來用,單純地認為線程中斷與線程阻塞的概念是一致的,都是值線程運行狀態的停止。其實這個觀點是錯誤的,兩者之前有很大的區別,下文就著重介紹兩者之間的區別。
線程中斷
在一個線程正常結束之前,如果被強制終止,那么就有可能造成一些比較嚴重的后果,設想一下如果現在有一個線程持有同步鎖,然后在沒有釋放鎖資源的情況下被強制休眠,那么這就造成了其他線程無法訪問同步代碼塊。因此我們可以看到在 JAVA 中類似 Thread#stop() 方法被標為 @Deprecated。
針對上述情況,我們不能直接將線程給終止掉,但有時又必須將讓線程停止運行某些代碼,那么此時我們必須有一種機制讓線程知道它該停止了。Java 為我們提供了一個比較優雅的做法,即可以通過 Thread#interrupt() 給線程該線程一個標志位,讓該線程自己決定該怎么辦。
接下來就用代碼來延時下 interrupt() 的作用:
Copypublic class InterruptDemo {
static class MyThread implements Runnable {
@Override
public void run() {
for (int i= 0; !Thread.currentThread().isInterrupted() && i < 200000; i++) {
System.out.println(Thread.currentThread().getName() + ":i = " + i);
}
}
}
public static void mAIn(String[] args) throws InterruptedException {
Thread myThread = new Thread(new MyThread());
myThread.start();
// 讓線程運行一段時間
Thread.sleep(5);
myThread.interrupt();
// 等待 myThread 運行停止
myThread.join();
System.out.println("end");
}
}
以上代碼的運行結果如下:
可以看到,當前線程并沒有按 for 循環中的結束量 20000 去跑,而是在被中斷后,停止了當前了 for 循環。所以我們可以利用 interrupt 配置線程使用,使得線程在一定的位置停止下來。
不過到這里可能會讓人產生一些疑惑,因為在這里看起來,當前線程像是被阻塞掉了,其實并不是的,我們可以利用下面這段代碼來演示下:
Copypublic class InterruptDemo {
static class MyThread implements Runnable {
@Override
public void run() {
for (int i= 0; i < 200000; i++) {
System.out.println(Thread.currentThread().getName() + ":i = " + i);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread myThread = new Thread(new MyThread());
myThread.start();
// 讓線程運行一段時間
Thread.sleep(5);
myThread.interrupt();
// 等待 myThread 運行停止
myThread.join();
System.out.println("end");
}
}
上面這段代碼的運行結果如下:
可見,線程一直打印到 20000,執行完畢后推出線程,并沒有像我們預料中在某處中斷。所以我們可以得出結論:單純用 interrupt() 中斷線程方法并不能停止當前正在運行的線程,需要配合其他方法才能正確停止線程。
了解完中斷的基本概念后,線程的中斷還有需要其他需要注意的點:
- 設置線程中斷后,線程內調用 wait()、join()、slepp() 方法中的一種,都會拋出 InterruptedException 異常,且中斷標志位被清除,重新設置為 false;
- 當線程被阻塞,比如調用了上述三個方法之一,那么此時調用它的 interrupt() 方法,也會產生一個 InterruptedException 異常。因為沒有占有 CPU 的線程是無法給自己設置中斷狀態位置的;
- 嘗試獲取一個內部鎖的操作(進入一個 synchronized 塊)是不能被中斷的,但是 ReentrantLock 支持可中斷的獲取模式:tryLock(long time, TimeUnit unit);
- 當代碼調用中需要拋出一個 InterruptedException,捕獲之后,要么繼續往上拋,要么重置中斷狀態,這是最安全的做法。
線程阻塞
上面講完了線程中斷,它其實只是一個標志位,并不能讓線程真正的停止下來,那么接下來就來介紹如何真正讓線程停止下來。
對于這個問題,Java 中提供了一個較為底層的并發工具類:LockSupport,該類中的核心方法有兩個:park(Object blocker) 以及 unpark(Thread thred),前者表示阻塞指定線程,后者表示喚醒指定的線程。
Copy// java.util.concurrent.locks.LockSupport
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
該方法在 Java 的語言層面上比較簡單,最終也是去調用 UNSAFE 中的 native 方法。真正涉及到底層的東西需要去理解 JVM 的源碼,這里就不做太多的介紹。不過我們可以用一個簡單的例子來演示下這兩個方法:
Copypublic class LockSupportDemo {
static class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "開始執行");
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "執行結束");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThread(), "線程:MyThread");
thread.start();
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "主線程執行中");
LockSupport.unpark(thread);
System.out.println(Thread.currentThread().getName() + "主線程執行結束");
}
}
上述代碼的執行結果為:
Copy線程:MyThread開始執行
main主線程執行中
線程:MyThread執行結束
main主線程執行結束
可以看到,myThread 線程在開始執行后停止了下來,等到主線程重新調用 LockSupport.unpark(thread) 后才重新開始執行。
原文鏈接:
https://mp.weixin.qq.com/s/NzEFLh0_6mA-2uBL7VzAVA