大家好,我是半夏之沫 😁😁 一名金融科技領域的JAVA系統研發😊😊
我希望將自己工作和學習中的經驗以最樸實,最嚴謹的方式分享給大家,共同進步👉💓👈
👉👉👉👉👉👉👉👉💓寫作不易,期待大家的關注和點贊💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓關注微信公眾號【技術探界】 💓👈👈👈👈👈👈👈👈
前言
Java中的線程,有一個狀態叫做 中斷狀態,用於標記線程是否被中斷過,通過對線程中斷狀態的判斷,可以實現例如 優雅終止線程 和 喚醒線程 等功能。
在Thread類中有interrupt(),interrupted()和isInterrupted() 方法與線程的中斷有關,本篇文章將對這些方法的具體作用進行詳細解釋。
在文章的最後,還會通過一個典型例子,演示如何通過線程的中斷狀態來優雅的終止線程。
正文
一. interrupt()方法詳解
Thread類提供了interrupt() 方法來中斷線程,哪個線程對象的interrupt() 方法被調用,那麼這個線程就會被中斷。關於interrupt() 方法,有如下注意點。
- interrupt() 方法是成員方法;
- 通常不能在線程內部調用線程的 interrupt() 方法來中斷線程自己;
- 如果線程正阻塞在 Object#wait,Object.wait(long),Object.wait(long, int),Thread#join,Thread.join(long),Thread.join(long, int),Thread.sleep(long) 或 Thread.sleep(long, int) 方法上,然後被 interrupt() 方法中斷,此時中斷狀態會被重置為 false(false 表示未被中斷),並且被中斷的線程會收到中斷異常 InterruptedException。
上面最重要的就是第3點,第3點中羅列出來的方法在使用時都需要try-catch中斷異常InterruptedException,但是在這個中斷異常被拋出前,線程的中斷狀態會被重置為false,這就造成一種現象,因為被中斷而從阻塞方法中喚醒的線程的中斷狀態是false。
二. interrupted()方法詳解
Thread類提供了靜態方法interrupted() 來得到調用該方法的線程的中斷狀態。關於interrupted() 方法,有如下注意點。
- interrupted() 方法是靜態方法;
- 哪個線程調用 interrupted() 方法,就會返回這個線程的中斷狀態,然後線程的中斷狀態會重置為 false。
interrupted() 方法在調用後,調用線程的中斷狀態會重置為false,這一點請切記。
三. isInterrupted()方法詳解
Thread類提供了isInterrupted() 方法來得到線程的中斷狀態,哪個線程對象的isInterrupted() 方法被調用,就會返回這個線程的中斷狀態。關於isInterrupted() 方法,有如下注意點。
- isInterrupted() 方法是成員方法;
- 哪個線程對象的 isInterrupted() 方法被調用,就會返回這個線程的中斷狀態,並且中斷狀態不會重置為 false。
isInterrupted() 方法和interrupted() 方法都能獲取到線程的中斷狀態,不同點在於isInterrupted() 方法獲取到線程的中斷狀態後,線程的中斷狀態不會重置為false,而interrupted() 方法會。
四. 優雅的終止線程
首先明確一點,一個線程的終止,不能由其它線程來強行終止,這也就是為什麼Thread#stop,Thread#suspend等方法會被廢棄。
優雅終止線程的簡單思路如下。
在線程執行的任務中不斷的判斷一個標誌位,當標誌位滿足某種條件時,任務結束運行,從而線程優雅終止。
如下是一個簡單示例。
public class InterruptedTest {
@Test
public void 優雅終止線程的簡單示例() {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
// 執行任務
for (int i = 0; i < 5; i++) {
threadPool.execute(() -> {
Thread thread = Thread.currentThread();
while (!thread.isInterrupted()) {
// empty run
}
System.out.println(thread.getName() + " 任務執行完畢");
});
}
// 主線程等待2秒,等待任務充分執行
LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);
// shutdownNow線程池
threadPool.shutdownNow();
// 主線程等待1秒,等待任務關閉
LockSupport.parkNanos(1000 * 1000 * 1000);
}
}
上述示例中,每個線程執行的任務中會一直判斷當前線程的中斷狀態,如果中斷狀態為true(表示線程被中斷了),那麼就退出while循環,從而任務執行完畢,最終線程被優雅終止。
運行測試程序,打印如下。
pool-1-thread-5 任務執行完畢
pool-1-thread-4 任務執行完畢
pool-1-thread-3 任務執行完畢
pool-1-thread-2 任務執行完畢
pool-1-thread-1 任務執行完畢
運行結果表明線程確實被終止了。上述示例中線程執行的任務實際上是 響應中斷 的,因此在線程被調用interrupt() 方法時,能夠響應中斷,從而結束任務的執行,最終線程終止。
再看如下一個例子,是一個看起來響應中斷,卻實際不響應中斷的典型錯誤案例。
public class InterruptedTest {
@Test
public void 線程池中的任務不響應中斷的典型案例() {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
// 執行任務
for (int i = 0; i < 5; i++) {
threadPool.execute(() -> {
Thread thread = Thread.currentThread();
while (!thread.isInterrupted()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(thread.getName() + " 被中斷了");
}
}
System.out.println(thread.getName() + " 任務執行完畢");
});
}
// 主線程等待2秒,等待任務充分執行
LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);
// shutdownNow線程池
threadPool.shutdownNow();
// 主線程等待1秒,等待任務關閉
LockSupport.parkNanos(1000 * 1000 * 1000);
}
}
上述示例中,任務大部分時間是阻塞在Thread.sleep(long) 方法上的,但其實Thread.sleep(long) 方法是響應中斷的,所以上述示例是希望線程被中斷時,線程從Thread.sleep(long) 方法返回,然後判斷線程的中斷狀態,然後結束任務的運行,最終線程終止。
運行測試程序,打印結果如下。
pool-1-thread-1 被中斷了
pool-1-thread-3 被中斷了
pool-1-thread-4 被中斷了
pool-1-thread-2 被中斷了
pool-1-thread-5 被中斷了
可見任務沒有結束運行,故線程也並沒有被終止。原因就是第一節中提到的,線程阻塞在Thread.sleep(long) 方法上時如果被中斷,那麼中斷狀態會被重置為false然後拋出中斷異常InterruptedException,那麼上述示例中,任務在判斷線程的中斷狀態時,中斷狀態會恆為false,故任務永遠不會退出。
改進代碼如下所示。
public class InterruptedTest {
@Test
public void 線程池中的任務不響應中斷的典型案例改進() {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 5,
60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
// 執行任務
for (int i = 0; i < 5; i++) {
threadPool.execute(() -> {
Thread thread = Thread.currentThread();
while (!thread.isInterrupted()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// 重新將中斷狀態置為true
thread.interrupt();
System.out.println(thread.getName() + " 被中斷了");
}
}
System.out.println(thread.getName() + " 任務執行完畢");
});
}
// 主線程等待2秒,等待任務充分執行
LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);
// shutdownNow線程池
threadPool.shutdownNow();
// 主線程等待1秒,等待任務關閉
LockSupport.parkNanos(1000 * 1000 * 1000);
}
}
改進思路就是在任務中捕獲到InterruptedException後,再調用一次當前線程對象的interrupt() 方法,來將當前線程的中斷狀態重新置為true,從而任務可以順利結束運行,最終線程終止。
運行測試程序,打印結果如下。
pool-1-thread-5 被中斷了
pool-1-thread-5 任務執行完畢
pool-1-thread-1 被中斷了
pool-1-thread-1 任務執行完畢
pool-1-thread-2 被中斷了
pool-1-thread-2 任務執行完畢
pool-1-thread-3 被中斷了
pool-1-thread-4 被中斷了
pool-1-thread-4 任務執行完畢
pool-1-thread-3 任務執行完畢
可見任務是順利執行完畢,從而線程完成了優雅終止。
總結
關於Java中線程的 中斷 總結如下。
- Thread 類提供了 interrupt() 成員方法來中斷線程。哪個線程對象的interrupt() 方法被調用,那麼這個線程被中斷,中斷狀態會被置為true。但是如果線程是阻塞在Object#wait,Thread#sleep等方法上時被調用interrupt() 方法來中斷,那麼中斷狀態會被重置為false,然後拋出中斷異常InterruptedException;
- Thread 類提供了 interrupted() 靜態方法來獲取線程的中斷狀態。哪個線程調用interrupted() 方法,就返回這個線程的中斷狀態,同時也
會重置這個線程的中斷狀態為false; - Thread 類提供了 isInterrupted() 成員方法來獲取線程的中斷狀態。哪個線程對象的isInterrupted() 方法被調用,那麼就返回這個線程的中斷狀態,並且
不會重置這個線程的中斷狀態為false;
線程的 優雅終止 思路如下。
在線程執行的任務中不斷的判斷一個標誌位,當標誌位滿足某種條件時,任務結束運行,從而線程優雅終止。
大家好,我是半夏之沫 😁😁 一名金融科技領域的JAVA系統研發😊😊
我希望將自己工作和學習中的經驗以最樸實,最嚴謹的方式分享給大家,共同進步👉💓👈
👉👉👉👉👉👉👉👉💓寫作不易,期待大家的關注和點贊💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓關注微信公眾號【技術探界】 💓👈👈👈👈👈👈👈👈