程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 詳解Java編程中對線程的中止處置

詳解Java編程中對線程的中止處置

編輯:關於JAVA

詳解Java編程中對線程的中止處置。本站提示廣大學習愛好者:(詳解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類供給了幾個辦法來操作這個中止狀況,這些辦法包含:

public static booleaninterrupted

測試以後線程能否曾經中止。線程的中止狀況 由該辦法消除。換句話說,假如持續兩次挪用該辦法,則第二次挪用將前往 false(在第一次挪用已消除了個中斷狀況以後,且第二次挪用磨練完中止狀況前,以後線程再次中止的情形除外)。

public booleanisInterrupted()

測試線程能否曾經中止。線程的中止狀況不受該辦法的影響。

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.
上面的法式會演示對非壅塞中的線程中止:

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();
  }
}

上面的法式演示的是子線程告訴父線程別等它了:

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的處所把狀況復位:

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的子類。

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

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外面做了一個異常耗時的排序操作,排序辦法中,只要簡略的加、減、賦值、比擬等操作,一個能夠的履行成果以下:

 
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並回車便可。

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)會使得全部法式加入。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved