程序員:還不知道怎麼正確的停止線程?

前言

線程在後臺開發中會經常用到,那你每次關閉線程都正確嗎?

特別是目前多線程開發場景越來越多,給程序員造成的困擾就更多了。。

今天我們就來了瞭如何正確停止線程。。。


程序員:還不知道怎麼正確的停止線程?


如何正確的停止線程?

  • 一個線程一般都是正常的運行直到執行任務完畢而結束。那什麼時候我們需要對線程進行停止的動作呢?例如用戶行為的一個取消動作,運行超時或者運行過程中出現錯誤也是需要停止當前線程,應用程序需要進行重啟等都需要我們主動的停止正在運行的線程,如何安全可靠的停止線程並不是一件簡單的事情。
  • java語言並沒有一種機制可以安全可靠的去停止一個線程,但是提供了一箇中斷(interrupt)機制,是通過啟動一個線程去通知當前真正運行的線程,告訴它你別運行了,可以停止了。而不是強者進行停止。
  • 我們都知道interrupt是中斷的意思,最終的決定權還是在被通知的線程,看它心情,可以停止也可以不停止。如果它不停止我們也無可奈何? 是不是拽!
  • 看到這裡,肯定有很多人疑惑,我們是程序的開發者,為什麼會作為停止線程的機制呢?我們看一下下圖的場景和解釋就很清晰了。
程序員:還不知道怎麼正確的停止線程?

java語言設計將線程停止的權利和步驟交給了被停止的線程本身。被停止的線程更加清楚具體什麼時機進行停止。

使用interrupt進行通知而不是強制停止

interrupt的正確使用方法

普通情況下

  • 如下代碼所示:
<code>public class MyNormalInterruptDemo {
public static void main(String[] args) {
Thread thread = new Thread(new MyNormalInterrupt());
thread.start();
//執行完進行中斷處理
thread.interrupt();
}
}

class MyNormalInterrupt implements Runnable {
@Override
public void run() {
System.out.println("線程開始執行了");

int i = 0;
while (i < Integer.MAX_VALUE){
i++;
}
System.out.println("最終執行的結果為:"+i);
}
}
/<code>

控制檯輸出

線程開始執行了

最終執行的結果為:2147483647


從結果上看最終程序還是正常執行了,雖然我們在調用端執行了interrupt中斷操作,但是我們在線程執行體中並沒有對中斷操作做處理,即使調用端發出了中斷信號,我們線程執行體並不理會它。接下來看一下如果在線程體內理會中斷操作。

<code>public class MyNormalInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyNormalInterrupt());
thread.start();
//執行完進行中斷處理
Thread.sleep(1000);
thread.interrupt();
}
}

class MyNormalInterrupt implements Runnable {
@Override
public void run() {
System.out.println("線程開始執行了");

int i = 0;
while (i < Integer.MAX_VALUE && !Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("最終執行的結果為:"+i);
}
}
/<code>

控制檯

線程開始執行了

最終執行的結果為:1103717170

顯然程序沒有執行完畢,因為最終的執行結果為2147483647,在由於在循環條件中添加了一個線程是否被中斷的條件,如何線程中斷了,循環結束線程執行完畢。

線程阻塞

代碼如下:

<code>public class MySleepInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MySleepInterrupt());
thread.start();
//執行完進行中斷處理
Thread.sleep(100);
thread.interrupt();
}
}


class MySleepInterrupt implements Runnable {
@Override
public void run() {
System.out.println("線程開始執行了");
try {
int i = 0;
while (i < Integer.MAX_VALUE && !Thread.currentThread().isInterrupted()) {
i++;
}
Thread.sleep(1000L);
System.out.println("最終執行的結果為:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}

}
}
/<code>

控制檯

線程開始執行了

java.lang.InterruptedException: sleep interrupted

at java.lang.Thread.sleep(Native Method)

at com.karl.concurrent.chapter07.MySleepInterrupt.run(MySleepInterruptDemo.java:28)

at java.lang.Thread.run(Thread.java:748)


在run方法內執行sleep方法時就會提示獲取InterruptedException異常

當線程正在休眠的過程中收到了中斷信號,響應的方式就是拋出InterruptedException異常。

線程迭代阻塞

代碼如下所示:

<code>public class MyEveryLoopSleepInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyEveryLoopSleepInterrupt());
thread.start();
//執行完進行中斷處理
Thread.sleep(100);
thread.interrupt();
}
}

class MyEveryLoopSleepInterrupt implements Runnable {
@Override

public void run() {
System.out.println("線程開始執行了");
try {
int i = 0;
while (i < Integer.MAX_VALUE && !Thread.currentThread().isInterrupted()) {
i++;
Thread.sleep(10L);
}
System.out.println("最終執行的結果為:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/<code>

控制檯

線程開始執行了

java.lang.InterruptedException: sleep interrupted

at java.lang.Thread.sleep(Native Method)

at com.karl.concurrent.chapter07.MyEveryLoopSleepInterrupt.run(MyEveryLoopSleepInterruptDemo.java:29)

at java.lang.Thread.run(Thread.java:748)


觀察一下上述代碼和線程阻塞的區別

  • 阻塞在循環體內
  • 在循環條件內不進行線程是否被中斷的判斷

原因: 因為在線程的執行體內,大部分的時候線程在休眠狀態,休眠狀態的線程被中斷會拋出InterruptedException異常,這時候循環條件的判斷其實就多餘了。所以不需要每次循環的過程中都檢查是否被中斷。因為在循環體內sleep會幫我們響應這個中斷。


interrupt的錯誤使用方法

如果在while裡面使用try catch 會導致中斷失效。代碼如下所示:

<code>public class MyErrorInterruptDemo {

public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyErrorInterrup());
thread.start();
//執行完進行中斷處理
Thread.sleep(100);
thread.interrupt();
}
}


class MyErrorInterrup implements Runnable {
@Override
public void run() {
System.out.println("線程開始執行了");

int i = 0;
while (i < Integer.MAX_VALUE && !Thread.currentThread().isInterrupted()) {
i++;
try {
Thread.sleep(10L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println("最終執行的結果為:" + i);
}
}
/<code>

控制檯

程序員:還不知道怎麼正確的停止線程?

為什麼線程一直在執行?

  • 很多小夥伴很快就想到了因為循環體內捕獲了異常,沒有拋出去所以一直在執行。
  • 那為什麼我們在循環條件加了是否被中斷這個判斷線程還在執行呢?面對第二個疑問小夥伴們可能就無法回答了。

原因如下所示:

因為java在設置Sleep方法機制的時候,一旦響應了中斷,就將線程的中斷標誌清楚,因為當我們響應中斷後,在繼續根據線程的中斷標記位作為判斷條件其實已經失效了。看到這裡肯定有不少的小夥伴恍然大悟的哦了一聲。

實際工作開發中的最近實踐

  • 上述一些簡單的案例為了幫助我們更好的理解中斷的原理,下面我們將講述在我們實際的開發過程中如何正確的中斷正在執行的線程。

第一種: 傳遞中斷(推薦)

  • 先看一下錯誤示範
<code>public class RightWayStopThreadInProd {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRightWayStopThreadInProd());
thread.start();
Thread.sleep(100);
thread.interrupt();
}
}

class MyRightWayStopThreadInProd implements Runnable{

@Override
public void run() {
System.out.println("開始執行業務");
while (true){
// 模擬在執行的過程中調用其他方法

this.method();
}
}

private void method(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/<code>

控制檯

程序員:還不知道怎麼正確的停止線程?

上述的代碼主要的問題我想大家也應該清楚了,在調用method()方法的過程中,由於終端響應被try/catch了run方法是無法感知的。所以線程在一直運行。

所以主要的問題在method方法,不能將中斷響應給自己吐了導致run方法感知不到,即使method方法自己無法解決中斷異常,正確的做法應該上報,在高層(調用method方法)自行根據情況處理而不是私下解決。

正確的代碼如下所示:

<code>public class RightWayStopThreadInProd {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRightWayStopThreadInProd());
thread.start();
Thread.sleep(100);
thread.interrupt();
}
}

class MyRightWayStopThreadInProd implements Runnable{

@Override
public void run() {
System.out.println("開始執行業務");
while (true){
// 模擬在執行的過程中調用其他方法
try {
this.method();
} catch (InterruptedException e) {
//因為在run方法中不能在往上進行拋錯,在這裡我們能做的就是保存日誌或者停止程序運行
System.out.println("保存日誌/停止程序運行");
e.printStackTrace();
}
}
}

private void method() throws InterruptedException {
Thread.sleep(1000);
}
}

/<code>

恢復中斷

當我們不想傳遞中斷或者無法中斷的時候我們可以進行恢復中斷處理。

在catch語句中在調用Thread.currentThread().interrupt()進行恢復中斷,以便在後續的執行中檢查是否發生了中斷。就是上述描述了自己吐了中斷在吐出來的意思。

具體代碼如下所示

<code>public class RightWayStopThreadInProd2 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRightWayStopThreadInProd());
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
}

class MyRightWayStopThreadInProd2 implements Runnable {

@Override
public void run() {
System.out.println("開始執行業務");
while (true) {
if(Thread.currentThread().isInterrupted()){
System.out.println("發生了中斷,退出執行");
break;
}
// 模擬在執行的過程中調用其他方法
this.method();
}
}

private void method() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {

Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}
/<code>

控制檯

開始執行業務

java.lang.InterruptedException: sleep interrupted

at java.lang.Thread.sleep(Native Method)

at com.karl.concurrent.chapter07.MyRightWayStopThreadInProd.method(RightWayStopThreadInProd.java:35)

at com.karl.concurrent.chapter07.MyRightWayStopThreadInProd.run(RightWayStopThreadInProd.java:29)

at java.lang.Thread.run(Thread.java:748)

發生了中斷,退出執行

這個時候需要我們自行在上層每次循環進行判斷是否發生了中斷。

響應中斷的其他方法

上述我們描述線程阻塞的響應中斷,類似的還有其他方法如下所示:

  • Object:wait()
  • Thread:join()
  • BlockingQueue:take()/put()
  • Lock:lockInterruptibly()
  • CountDownLatch:await()
  • CyclicBarrier:await()
  • 等…

總結

看到這裡,相信大家對線程中斷有了一定的認知。本人能力和表達有限,有不足的地方望及時指正。


分享到:


相關文章: