JAVA並發編程9_正確關閉線程,java並發線程

來源:互聯網
上載者:User

JAVA並發編程9_正確關閉線程,java並發線程

Java沒有提供任何的安全機制來終止線程,程,雖然Thread.stop和suspend等方法提供了這樣的機制,但是存在嚴重的缺陷,應該避免使用這些方法。
它提供了中斷(interrupt),這是一種協作機制,能夠使一個線程終止另一個線程當前的工作。
一種協作機制是設定一個“已請求取消(Cancellation Requested)”標誌,而任務定期查看該標誌。

“已請求取消”標誌
private volatile boolean cancelled;public void run() {      while (!cancelled) {        doSomething();    }}public void cancel() {    cancelled = true;}

這種方式通過輪詢該標誌確定是否繼續執行該任務。如果該標誌cancel未被調用,那麼該任務將一直運行下去。Volatile保證了標誌的可見度,使得這個過程可以可靠地工作。
Cancel的取消機制可以使該任務退出,但在退出過程中需要花費一定的時間。極端情況下當任務調用了一個阻塞方法,比如sleep/wait/…那麼任務可能永遠不會檢查取消標誌,因此永遠不會結束。

public class CloseThread extends Thread {      private volatile boolean cancelled;     public void run() {          while (!cancelled) {            doSomething();        }    }      private void doSomething() {        try {            Thread.sleep(10000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }    public void cancel() {          cancelled = true;    }    public static void main(String[] args) {        final CloseThread closeThread = new CloseThread();        closeThread.start();        Timer timer = new Timer();        timer.schedule(new TimerTask() {            @Override            public void run() {                closeThread.cancel();                System.out.println("closeThread isAlive --" + closeThread.isAlive());            }        }, 2000);    }}

列印如下:

closeThread isAlive –true

在run方法裡面輪詢狀態標誌,當cancelled為false時就繼續執行任務,在2S之後調用cancel方法將cancelled標誌置為true。
可以看到當在run方法裡面耗時比較長的時,每10S才能輪詢一次,即退出任務需要花費一定的時間,此時調用並不能關閉線程,在10S後線程才能關閉。極限情況下,即遇到阻塞方法時,線程永遠關閉不了。
因此這種設定標誌的方式存在問題,自訂的取消機制無法與可阻塞的方法實現良好的互動。

使用中斷關閉線程

通常,中斷是實現取消的最合理方式。

中斷是一種協作機制,線程可以通過這種機制來通知另一個線程,告訴他再合適的或者可能的情況下停止當前工作,並轉而執行其他的工作。

public class Thread{    public void interrupt() { ... }   //中斷一個線程    public boolean isInterrupted() { ... }  //擷取中斷標誌,判斷是否中斷    public static boolean interrupted() { ... }  //清除中斷狀態,並返回它之前的狀態值}

每個線程都有一個boolean類型的中斷狀態。當中斷線程時,這個線程的中斷狀態將被設定成true。
調用interrupt並不意味著立即停止目標線程進行中的工作,而只是傳遞了請求中斷的訊息,換句話說,僅僅修改了線程的標誌欄位。
對中斷操作的正確理解是:他並不會真正的中斷一個正在啟動並執行線程,而只是發出了插斷要求,然後由線程在下一個合適的時刻(取消點)中斷自己。比如sleep/wait/join等將嚴格的處理這種請求。當他們收到插斷要求或者在開始執行的時候發現某個已被設定好的中斷狀態時,將拋出一個異常。

使用中斷取消線程任務分三種情況

- 非阻塞狀態下發生中斷
- 阻塞狀態下可中斷的阻塞
- 阻塞狀態下不可中斷的阻塞

非阻塞狀態下發生中斷

當線程在非阻塞狀態下中斷的時候,它的中斷狀態將被設定,如果不觸發InterruptedException,那麼中斷狀態將一直保持,然後根據檢查中斷狀態來判斷是否發生了中斷。
可以通過在任務代碼中輪詢當前線程的中斷狀態,要選擇合適的輪詢頻率,就需要在效率和響應性之間權衡。

阻塞狀態下可中斷的阻塞 阻塞庫函數中斷

當wait/sleep/BlockingQueue.put等阻塞庫的方法會在發現中斷時提前返回,它們在中斷的時候執行的操作包括:清除中斷狀態,拋出InterruptException。

public void run() {      while (!Thread.currentThread().isInterrupted()) {        doSomething();    }}  private void doSomething() {    try {        Thread.sleep(100 * 1000);        // 阻塞操作    } catch (InterruptedException e) {        // ?????// 要有對應的中斷響應    }}public void cancel() {    interrupt();}

while裡面顯式的檢測對中斷具有更高的響應性。

如何響應中斷?

響應中斷的方式
1. 傳遞異常,從而使調用方法也成為可中斷的阻塞方法。簡單說就是添加拋出異常的聲明。
2. 恢複中斷狀態,從而使調用棧的上層代碼能夠對其進行處理。標準的做法就是在catch裡面再次調用interrupt來恢複中斷狀態,從而讓上層代碼能夠對其進行處理。
上面的樣本中在catch裡面可以再次調用interrupt來恢複中斷狀態,當再次while迴圈時無法滿足條件就會退出,考慮下面的樣本
當捕獲到InterruptException時除非在代碼裡實現了中斷策略才可以屏蔽中斷。中斷策略指的是收到插斷要求之後該怎麼做?比如下面的中斷策略就是線程會馬上退出,沒有上層代碼需要知道中斷資訊。

// InterruptException中屏蔽中斷,是因為他已經知道線程將要結束了public void run() {      try {        while (!Thread.currentThread().isInterrupted()) {                Thread.sleep(10 * 1000);        }    } catch (InterruptedException e) {        /* 允許線程退出  */    }}  public void cancel() {    interrupt();}
不支援取消但仍可調用可中斷阻塞方法的情況

不支援取消但仍可調用可中斷阻塞方法的操作。當catch到中斷時應該本地儲存中斷狀態,並在返回前恢複中斷狀態而不要在捕獲到中斷狀態時恢複,如果過早的設定中斷狀態有可能引起無限迴圈,因為大多數可中斷的阻塞方法在入口處檢查中斷狀態,並且當發現該狀態已被設定時立即拋出InterruptException

public Task getNextTask(BlockingQueue<Task> queue) {      boolean interrupted = false;      try {          while (true) {              try {                  return queue.take();              } catch (InterruptedException e) {                  interrupted = true;              }          }      } finally {          if (interrupted)              Thread.currentThread().interrupt();      }  }
阻塞狀態下不可中斷的阻塞

許多阻塞的方法都是通過提前返回或者拋出InterruptException來響應插斷要求的,從而使開發人員能容易的構建出能取消請求的任務,然而並非所有的可兒阻塞方法或者阻塞機制都能響應中斷。比如socket I/O、等待擷取內建鎖,插斷要求只能設定線程的中斷狀態,除此之外沒有其他任何作用。我們可以使用類似中斷的手段來停止這些線程,但這要求知道線程阻塞的原因。
同步Socket I/O 雖然InputStream和OutputStream中的read和write方法都不會想要中斷,但通過關閉底層通訊端,可以使得由於執行read和write等方法而被阻塞的線程拋出一個SocketException。也就是在需要取消任務的地方關閉底層通訊端,在SocketException裡面就可以響應中斷。

public class CloseSocket extends Thread {      private Socket socket;      private InputStream in;    public  CloseSocket(Socket socket) throws IOException {        this.socket = socket;        this.in = socket.getInputStream();    }    @Override    public void interrupt() {        try {            // Closing this socket will also close the socket's InputStream and OutputStream.             socket.close();        } catch (IOException e) {        } finally{            super.interrupt();        }    }    @Override    public void run() {        try {            byte [] buf = new byte[1024];            while (true) {                int num = in.read(buf);                // ......            }        } catch (IOException e) {            // 線程直接退出好了        }    }}  

改寫了interrupt方法,使其技能處理標準的中斷也能關閉底層通訊端,因此無論線程是在read方法中阻塞還是在某個可中斷的阻塞方法中阻塞,都可以被中斷並停止當前的工作。

關於Thread.stop和suspend不建議使用 Java Thread Primitive Deprecation

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.