一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服務器之家 - 編程語言 - JAVA教程 - 詳解Java編程中對線程的中斷處理

詳解Java編程中對線程的中斷處理

2020-03-02 12:32turkeyzhou JAVA教程

這篇文章主要介紹了Java編程中對線程的中斷處理,特別講解了中斷的時機與中斷狀態的管理,需要的朋友可以參考下

1. 引言

當我們點擊某個殺毒軟件的取消按鈕來停止查殺病毒時,當我們在控制臺敲入quit命令以結束某個后臺服務時……都需要通過一個線程去取消另一個線程正在執行的任務。Java沒有提供一種安全直接的方法來停止某個線程,但是Java提供了中斷機制。

如果對Java中斷沒有一個全面的了解,可能會誤以為被中斷的線程將立馬退出運行,但事實并非如此。中斷機制是如何工作的?捕獲或檢測到中斷后,是拋出InterruptedException還是重設中斷狀態以及在方法中吞掉中斷狀態會有什么后果?Thread.stop與中斷相比又有哪些異同?什么情況下需要使用中斷?本文將從以上幾個方面進行描述。

2. 中斷的原理

Java中斷機制是一種協作機制,也就是說通過中斷并不能直接終止另一個線程,而需要被中斷的線程自己處理中斷。這好比是家里的父母叮囑在外的子女要注意身體,但子女是否注意身體,怎么注意身體則完全取決于自己。

Java中斷模型也是這么簡單,每個線程對象里都有一個boolean類型的標識(不一定就要是Thread類的字段,實際上也的確不是,這幾個方法最終都是通過native方法來完成的),代表著是否有中斷請求(該請求可以來自所有線程,包括被中斷的線程本身)。例如,當線程t1想中斷線程t2,只需要在線程t1中將線程t2對象的中斷標識置為true,然后線程2可以選擇在合適的時候處理該中斷請求,甚至可以不理會該請求,就像這個線程沒有被中斷一樣。

java.lang.Thread類提供了幾個方法來操作這個中斷狀態,這些方法包括:

?
1
public static booleaninterrupted

測試當前線程是否已經中斷。線程的中斷狀態 由該方法清除。換句話說,如果連續兩次調用該方法,則第二次調用將返回 false(在第一次調用已清除了其中斷狀態之后,且第二次調用檢驗完中斷狀態前,當前線程再次中斷的情況除外)。

?
1
public booleanisInterrupted()

測試線程是否已經中斷。線程的中斷狀態不受該方法的影響。

?
1
public void interrupt()

中斷線程。
其中,interrupt方法是唯一能將中斷狀態設置為true的方法。靜態方法interrupted會將當前線程的中斷狀態清除,但這個方法的命名極不直觀,很容易造成誤解,需要特別注意。

上面的例子中,線程t1通過調用interrupt方法將線程t2的中斷狀態置為true,t2可以在合適的時候調用interrupted或isInterrupted來檢測狀態并做相應的處理。

此外,類庫中的有些類的方法也可能會調用中斷,如FutureTask中的cancel方法,如果傳入的參數為true,它將會在正在運行異步任務的線程上調用interrupt方法,如果正在執行的異步任務中的代碼沒有對中斷做出響應,那么cancel方法中的參數將不會起到什么效果;又如ThreadPoolExecutor中的shutdownNow方法會遍歷線程池中的工作線程并調用線程的interrupt方法來中斷線程,所以如果工作線程中正在執行的任務沒有對中斷做出響應,任務將一直執行直到正常結束。

3. 中斷的處理

既然Java中斷機制只是設置被中斷線程的中斷狀態,那么被中斷線程該做些什么?

處理時機

顯然,作為一種協作機制,不會強求被中斷線程一定要在某個點進行處理。實際上,被中斷線程只需在合適的時候處理即可,如果沒有合適的時間點,甚至可以不處理,這時候在任務處理層面,就跟沒有調用中斷方法一樣。“合適的時候”與線程正在處理的業務邏輯緊密相關,例如,每次迭代的時候,進入一個可能阻塞且無法中斷的方法之前等,但多半不會出現在某個臨界區更新另一個對象狀態的時候,因為這可能會導致對象處于不一致狀態。

處理時機決定著程序的效率與中斷響應的靈敏性。頻繁的檢查中斷狀態可能會使程序執行效率下降,相反,檢查的較少可能使中斷請求得不到及時響應。如果發出中斷請求之后,被中斷的線程繼續執行一段時間不會給系統帶來災難,那么就可以將中斷處理放到方便檢查中斷,同時又能從一定程度上保證響應靈敏度的地方。當程序的性能指標比較關鍵時,可能需要建立一個測試模型來分析最佳的中斷檢測點,以平衡性能和響應靈敏性。

一個線程在未正常結束之前, 被強制終止是很危險的事情. 因為它可能帶來完全預料不到的嚴重后果. 所以你看到Thread.suspend, Thread.stop等方法都被Deprecated了.
那么不能直接把一個線程搞掛掉, 但有時候又有必要讓一個線程死掉, 或者讓它結束某種等待的狀態 該怎么辦呢? 優雅的方法就是, 給那個線程一個中斷信號, 讓它自己決定該怎么辦. 比如說, 在某個子線程中為了等待一些特定條件的到來, 你調用了Thread.sleep(10000), 預期線程睡10秒之后自己醒來, 但是如果這個特定條件提前到來的話, 你怎么通知一個在睡覺的線程呢? 又比如說, 主線程通過調用子線程的join方法阻塞自己以等待子線程結束, 但是子線程運行過程中發現自己沒辦法在短時間內結束, 于是它需要想辦法告訴主線程別等我了. 這些情況下, 就需要中斷.
中斷是通過調用Thread.interrupt()方法來做的. 這個方法通過修改了被調用線程的中斷狀態來告知那個線程, 說它被中斷了. 對于非阻塞中的線程, 只是改變了中斷狀態, 即Thread.isInterrupted()將返回true; 對于可取消的阻塞狀態中的線程, 比如等待在這些函數上的線程, Thread.sleep(), Object.wait(), Thread.join(), 這個線程收到中斷信號后, 會拋出InterruptedException, 同時會把中斷狀態置回為false.
下面的程序會演示對非阻塞中的線程中斷:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Thread3 extends Thread{
  public void run(){
    while(true){
      if(Thread.interrupted()){
        System.out.println("Someone interrupted me.");
      }
      else{
        System.out.println("Going...");
      }
      long now = System.currentTimeMillis();
      while(System.currentTimeMillis()-now<1000){
        // 為了避免Thread.sleep()而需要捕獲InterruptedException而帶來的理解上的困惑,
        // 此處用這種方法空轉1秒
      }
    }
  }
  
  public static void main(String[] args) throws InterruptedException {
    Thread3 t = new Thread3();
    t.start();
    Thread.sleep(3000);
    t.interrupt();
  }
}

下面的程序演示的是子線程通知父線程別等它了:

?
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
public class Thread4 extends Thread {
  private Thread parent;
  public Thread4(Thread parent){
    this.parent = parent;
  }
  
  public void run() {
    while (true) {
      System.out.println("sub thread is running...");
      long now = System.currentTimeMillis();
      while (System.currentTimeMillis() - now < 2000) {
        // 為了避免Thread.sleep()而需要捕獲InterruptedException而帶來的理解上的困惑,
        // 此處用這種方法空轉2秒
      }
      parent.interrupt();
    }
  }
  
  public static void main(String[] args){
    Thread4 t = new Thread4(Thread.currentThread());
    t.start();
    try {
      t.join();
    } catch (InterruptedException e) {
      System.out.println("Parent thread will die...");
    }
  }
}

中斷狀態可以通過 Thread.isInterrupted()來讀取,并且可以通過一個名為 Thread.interrupted()的靜態方法讀取和清除狀態(即調用該方法結束之后, 中斷狀態會變成false)。
由于處于阻塞狀態的線程 被中斷后拋出exception并置回中斷狀態, 有時候是不利的, 因為這個中斷狀態可能會作為別的線程的判斷條件, 所以穩妥的辦法是在處理exception的地方把狀態復位:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
boolean interrupted = false;
try {
  while (true) {
    try {
      return blockingQueue.take();
    } catch (InterruptedException e) {
      interrupted = true;
    }
  }
} finally {
  if (interrupted){
    Thread.currentThread().interrupt();
  }
}

當代碼調用中須要拋出一個InterruptedException, 你可以選擇把中斷狀態復位, 也可以選擇向外拋出InterruptedException, 由外層的調用者來決定.
不是所有的阻塞方法收到中斷后都可以取消阻塞狀態, 輸入和輸出流類會阻塞等待 I/O 完成,但是它們不拋出 InterruptedException,而且在被中斷的情況下也不會退出阻塞狀態.
嘗試獲取一個內部鎖的操作(進入一個 synchronized 塊)是不能被中斷的,但是 ReentrantLock 支持可中斷的獲取模式即 tryLock(long time, TimeUnit unit)。

處理方式

(1)、 中斷狀態的管理

一般說來,當可能阻塞的方法聲明中有拋出InterruptedException則暗示該方法是可中斷的,如BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep等,如果程序捕獲到這些可中斷的阻塞方法拋出的InterruptedException或檢測到中斷后,這些中斷信息該如何處理?一般有以下兩個通用原則:

如果遇到的是可中斷的阻塞方法拋出InterruptedException,可以繼續向方法調用棧的上層拋出該異常,如果是檢測到中斷,則可清除中斷狀態并拋出InterruptedException,使當前方法也成為一個可中斷的方法。
若有時候不太方便在方法上拋出InterruptedException,比如要實現的某個接口中的方法簽名上沒有throws InterruptedException,這時就可以捕獲可中斷方法的InterruptedException并通過Thread.currentThread.interrupt()來重新設置中斷狀態。如果是檢測并清除了中斷狀態,亦是如此。
一般的代碼中,尤其是作為一個基礎類庫時,絕不應當吞掉中斷,即捕獲到InterruptedException后在catch里什么也不做,清除中斷狀態后又不重設中斷狀態也不拋出InterruptedException等。因為吞掉中斷狀態會導致方法調用棧的上層得不到這些信息。

當然,凡事總有例外的時候,當你完全清楚自己的方法會被誰調用,而調用者也不會因為中斷被吞掉了而遇到麻煩,就可以這么做。

總得來說,就是要讓方法調用棧的上層獲知中斷的發生。假設你寫了一個類庫,類庫里有個方法amethod,在amethod中檢測并清除了中斷狀態,而沒有拋出InterruptedException,作為amethod的用戶來說,他并不知道里面的細節,如果用戶在調用amethod后也要使用中斷來做些事情,那么在調用amethod之后他將永遠也檢測不到中斷了,因為中斷信息已經被amethod清除掉了。如果作為用戶,遇到這樣有問題的類庫,又不能修改代碼,那該怎么處理?只好在自己的類里設置一個自己的中斷狀態,在調用interrupt方法的時候,同時設置該狀態,這實在是無路可走時才使用的方法。

(2)、 中斷的響應

程序里發現中斷后該怎么響應?這就得視實際情況而定了。有些程序可能一檢測到中斷就立馬將線程終止,有些可能是退出當前執行的任務,繼續執行下一個任務……作為一種協作機制,這要與中斷方協商好,當調用interrupt會發生些什么都是事先知道的,如做一些事務回滾操作,一些清理工作,一些補償操作等。若不確定調用某個線程的interrupt后該線程會做出什么樣的響應,那就不應當中斷該線程。

4. Thread.interrupt VS Thread.stop

Thread.stop方法已經不推薦使用了。而在某些方面Thread.stop與中斷機制有著相似之處。如當線程在等待內置鎖或IO時,stop跟interrupt一樣,不會中止這些操作;當catch住stop導致的異常時,程序也可以繼續執行,雖然stop本意是要停止線程,這么做會讓程序行為變得更加混亂。

那么它們的區別在哪里?最重要的就是中斷需要程序自己去檢測然后做相應的處理,而Thread.stop會直接在代碼執行過程中拋出ThreadDeath錯誤,這是一個java.lang.Error的子類。

在繼續之前,先來看個小例子:

?
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
package com.ticmy.interrupt;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class TestStop {
 private static final int[] array = new int[80000];
 private static final Thread t = new Thread() {
 public void run() {
  try {
  System.out.println(sort(array));
  } catch (Error err) {
  err.printStackTrace();
  }
  System.out.println("in thread t");
 }
 };
 
 static {
 Random random = new Random();
 for(int i = 0; i < array.length; i++) {
  array[i] = random.nextInt(i + 1);
 }
 }
 
 private static int sort(int[] array) {
 for (int i = 0; i < array.length-1; i++){
  for(int j = 0 ;j < array.length - i - 1; j++){
  if(array[j] < array[j + 1]){
   int temp = array[j];
   array[j] = array[j + 1];
   array[j + 1] = temp;
  }
  }
 }
 return array[0];
 }
 
 public static void main(String[] args) throws Exception {
 t.start();
 TimeUnit.SECONDS.sleep(1);
 System.out.println("go to stop thread t");
 t.stop();
 System.out.println("finish main");
 }
}

這個例子很簡單,線程t里面做了一個非常耗時的排序操作,排序方法中,只有簡單的加、減、賦值、比較等操作,一個可能的執行結果如下:

?
1
2
3
4
5
6
go to stop thread t
java.lang.ThreadDeath
 at java.lang.Thread.stop(Thread.java:758)
 at com.ticmy.interrupt.TestStop.main(TestStop.java:44)
finish main
in thread t

這里sort方法是個非常耗時的操作,也就是說主線程休眠一秒鐘后調用stop的時候,線程t還在執行sort方法。就是這樣一個簡單的方法,也會拋出錯誤!換一句話說,調用stop后,大部分Java字節碼都有可能拋出錯誤,哪怕是簡單的加法!

如果線程當前正持有鎖,stop之后則會釋放該鎖。由于此錯誤可能出現在很多地方,那么這就讓編程人員防不勝防,極易造成對象狀態的不一致。例如,對象obj中存放著一個范圍值:最小值low,最大值high,且low不得大于high,這種關系由鎖lock保護,以避免并發時產生競態條件而導致該關系失效。假設當前low值是5,high值是10,當線程t獲取lock后,將low值更新為了15,此時被stop了,真是糟糕,如果沒有捕獲住stop導致的Error,low的值就為15,high還是10,這導致它們之間的小于關系得不到保證,也就是對象狀態被破壞了!如果在給low賦值的時候catch住stop導致的Error則可能使后面high變量的賦值繼續,但是誰也不知道Error會在哪條語句拋出,如果對象狀態之間的關系更復雜呢?這種方式幾乎是無法維護的,太復雜了!如果是中斷操作,它決計不會在執行low賦值的時候拋出錯誤,這樣程序對于對象狀態一致性就是可控的。

正是因為可能導致對象狀態不一致,stop才被禁用。

5. 中斷的使用

通常,中斷的使用場景有以下幾個:

點擊某個桌面應用中的取消按鈕時;
某個操作超過了一定的執行時間限制需要中止時;
多個線程做相同的事情,只要一個線程成功其它線程都可以取消時;
一組線程中的一個或多個出現錯誤導致整組都無法繼續時;
當一個應用或服務需要停止時。
下面來看一個具體的例子。這個例子里,本打算采用GUI形式,但考慮到GUI代碼會使程序復雜化,就使用控制臺來模擬下核心的邏輯。這里新建了一個磁盤文件掃描的任務,掃描某個目錄下的所有文件并將文件路徑打印到控制臺,掃描的過程可能會很長。若需要中止該任務,只需在控制臺鍵入quit并回車即可。

?
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
50
51
52
53
54
55
56
57
58
59
60
61
package com.ticmy.interrupt;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
 
public class FileScanner {
 private static void listFile(File f) throws InterruptedException {
 if(f == null) {
  throw new IllegalArgumentException();
 }
 if(f.isFile()) {
  System.out.println(f);
  return;
 }
 File[] allFiles = f.listFiles();
 if(Thread.interrupted()) {
  throw new InterruptedException("文件掃描任務被中斷");
 }
 for(File file : allFiles) {
  //還可以將中斷檢測放到這里
  listFile(file);
 }
 }
 
 public static String readFromConsole() {
 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
 try {
  return reader.readLine();
 } catch (Exception e) {
  e.printStackTrace();
  return "";
 }
 }
 
 public static void main(String[] args) throws Exception {
 final Thread fileIteratorThread = new Thread() {
  public void run() {
  try {
   listFile(new File("c:\\"));
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  }
 };
 new Thread() {
  public void run() {
  while(true) {
   if("quit".equalsIgnoreCase(readFromConsole())) {
   if(fileIteratorThread.isAlive()) {
    fileIteratorThread.interrupt();
    return;
   }
   } else {
   System.out.println("輸入quit退出文件掃描");
   }
  }
  }
 }.start();
 fileIteratorThread.start();
 }
}

在掃描文件的過程中,對于中斷的檢測這里采用的策略是,如果碰到的是文件就不檢測中斷,是目錄才檢測中斷,因為文件可能是非常多的,每次遇到文件都檢測一次會降低程序執行效率。此外,在fileIteratorThread線程中,僅是捕獲了InterruptedException,沒有重設中斷狀態也沒有繼續拋出異常,因為我非常清楚它的使用環境,run方法的調用棧上層已經沒有可能需要檢測中斷狀態的方法了。

在這個程序中,輸入quit完全可以執行System.exit(0)操作來退出程序,但正如前面提到的,這是個GUI程序核心邏輯的模擬,在GUI中,執行System.exit(0)會使得整個程序退出。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 99久久一区二区精品 | 99久久精品免费看国产一区 | fc2免费人成在线 | xnxx老师 | 向日葵视频app下载18岁以下勿看 | 男同桌扒开女同桌胸罩喝奶 | 波多野结衣家庭教师 | 国产xxx在线 | 国产精品久久久久久久久齐齐 | 国产一区二区三区在线观看视频 | 成年男人永久免费看片 | 黑人又大又硬又粗再深一点 | 美女的让男人桶爽30分钟的 | 精新精新国产自在现拍 | 精品一区二区三区免费视频 | 4虎tv| 99ri国产精品| 91在线亚洲精品一区 | 狠狠色狠狠色综合曰曰 | 亚洲国产99在线精品一区二区 | 国产在线精品成人一区二区三区 | 门房秦大爷最新章节阅读 | 奇米影视7777久久精品 | 久久中文字幕亚洲 | 亚洲AV久久无码精品蜜桃 | 草草影院免费 | 99在线视频精品 | 亚洲国产欧美久久香综合 | 7788理论片在线观看 | 日韩视频一区二区三区 | 亚洲国产精品一区二区首页 | 国产日产精品久久久久快鸭 | 扒开老师两片湿漉的肉 | 亚洲无线一二三区2021 | 亚洲天天做夜夜做天天欢 | 国产在线视频一区二区三区 | 冰山美人调教耻辱h | 极品一区 | 国产精品成人网红女主播 | 国产91第一页 | 免费观看国产精品 |