天天看點

中斷線程詳解(Interrupt)

在上篇文章《多線程的使用——Thread類和Runnable接口》中提到中斷線程的問題。在JAVA中,曾經使用stop方法來停止線程,然而,該方法具有固有的不安全性,因而已經被抛棄(Deprecated)。那麼應該怎麼結束一個程序呢?官方文檔中對此有詳細說明:《為何不贊成使用 Thread.stop、Thread.suspend 和 Thread.resume?》。在此引用stop方法的說明:

1. Why is Thread.stop deprecated?

Because it is inherently unsafe. Stopping a thread causes it to unlock all the monitors that it has locked. (The monitors are unlocked as the ThreadDeath exception propagates up the stack.) If any of the objects previously protected by these monitors were in an inconsistent state, other threads may now view these objects in an inconsistent state. Such objects are said to be damaged. When threads operate on damaged objects, arbitrary behavior can result. This behavior may be subtle and difficult to detect, or it may be pronounced. Unlike other unchecked exceptions, ThreadDeath kills threads silently; thus, the user has no warning that his program may be corrupted. The corruption can manifest itself at any time after the actual damage occurs, even hours or days in the future.

大概意思是:

因為該方法本質上是不安全的。停止一個線程将釋放它已經鎖定的所有螢幕(作為沿堆棧向上傳播的未檢查 ThreadDeath 異常的一個自然後果)。如果以前受這些螢幕保護的任何對象都處于一種不一緻的狀态,則損壞的對象将對其他線程可見,這有可能導緻任意的行為。此行為可能是微妙的,難以察覺,也可能是顯著的。不像其他的未檢查異常,ThreadDeath異常會在背景殺死線程,是以,使用者并不會得到警告,提示他的程式可能已損壞。這種損壞有可能在實際破壞發生之後的任何時間表現出來,也有可能在多小時甚至在未來的很多天後。

在文檔中還提到,程式員不能通過捕獲ThreadDeath異常來修複已破壞的對象。具體原因見原文。

既然stop方法不建議使用,那麼應該用什麼方法來代理stop已實作相應的功能呢?

1、通過修改共享變量來通知目标線程停止運作

大部分需要使用stop的地方應該使用這種方法來達到中斷線程的目的。

這種方法有幾個要求或注意事項:

(1)目标線程必須有規律的檢查變量,當該變量訓示它應該停止運作時,該線程應該按一定的順序從它執行的方法中傳回。

(2)該變量必須定義為volatile,或者所有對它的通路必須同步(synchronized)。

例如:

假如你的applet包括start,stop,run幾個方法:

  1. private Thread blinker; 
  2. public void start() { 
  3.     blinker = new Thread(this); 
  4.     blinker.start(); 
  5. public void stop() { 
  6.     blinker.stop();  // UNSAFE! 
  7. public void run() { 
  8.     Thread thisThread = Thread.currentThread(); 
  9.     while (true) { 
  10.         try { 
  11.         thisThread.sleep(interval); 
  12.         } catch (InterruptedException e){ 
  13.         } 
  14.         repaint(); 
  15.     } 

你可以使用如下方式避免使用Thread.stop方法:

  1. private volatile Thread blinker; 
  2. public void stop() { 
  3.     blinker = null; 
  4. public void run() { 
  5.     Thread thisThread = Thread.currentThread(); 
  6.     while (blinker == thisThread) { 
  7.         try { 
  8.             thisThread.sleep(interval); 
  9.         } catch (InterruptedException e){ 
  10.         } 
  11.         repaint(); 
  12.     } 

2、通過Thread.interrupt方法中斷線程

通常情況下,我們應該使用第一種方式來代替Thread.stop方法。然而以下幾種方式應該使用Thread.interrupt方法來中斷線程(該方法通常也會結合第一種方法使用)。

一開始使用interrupt方法時,會有莫名奇妙的感覺:難道該方法有問題?

API文檔上說,該方法用于"Interrupts this thread"。請看下面的例子:

  1. package com.polaris.thread; 
  2. public class TestThread implements Runnable{ 
  3.     boolean stop = false; 
  4.     public static void main(String[] args) throws Exception { 
  5.         Thread thread = new Thread(new TestThread(),"My Thread"); 
  6.         System.out.println( "Starting thread..." ); 
  7.         thread.start(); 
  8.         Thread.sleep( 3000 ); 
  9.         System.out.println( "Interrupting thread..." ); 
  10.         thread.interrupt(); 
  11.         System.out.println("線程是否中斷:" + thread.isInterrupted()); 
  12.         Thread.sleep( 3000 ); 
  13.         System.out.println("Stopping application..." ); 
  14.     } 
  15.     public void run() { 
  16.         while(!stop){ 
  17.             System.out.println( "My Thread is running..." ); 
  18.             // 讓該循環持續一段時間,使上面的話列印次數少點 
  19.             long time = System.currentTimeMillis(); 
  20.             while((System.currentTimeMillis()-time < 1000)) { 
  21.             } 
  22.         } 
  23.         System.out.println("My Thread exiting under request..." ); 
  24.     } 

運作後的結果是:

Starting thread...

My Thread is running...

My Thread is running...

My Thread is running...

My Thread is running...

Interrupting thread...

線程是否中斷:true

My Thread is running...

My Thread is running...

My Thread is running...

Stopping application...

My Thread is running...

My Thread is running...

……

應用程式并不會退出,啟動的線程沒有因為調用interrupt而終止,可是從調用isInterrupted方法傳回的結果可以清楚地知道該線程已經中斷了。那位什麼會出現這種情況呢?到底是interrupt方法出問題了還是isInterrupted方法出問題了?在Thread類中還有一個測試中斷狀态的方法(靜态的)interrupted,換用這個方法測試,得到的結果是一樣的。由此似乎應該是interrupt方法出問題了。于是,在網上有一篇文章:《 Java Thread.interrupt 害人! 中斷JAVA線程》,它詳細的說明了應該如何使用interrupt來中斷一個線程的執行。

實際上,在JAVA API文檔中對該方法進行了詳細的說明。該方法實際上隻是設定了一個中斷狀态,當該線程由于下列原因而受阻時,這個中斷狀态就起作用了:

(1)如果線程在調用 Object 類的 wait()、wait(long) 或 wait(long, int) 方法,或者該類的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法過程中受阻,則其中斷狀态将被清除,它還将收到一個InterruptedException異常。這個時候,我們可以通過捕獲InterruptedException異常來終止線程的執行,具體可以通過return等退出或改變共享變量的值使其退出。

(2)如果該線程在可中斷的通道上的 I/O 操作中受阻,則該通道将被關閉,該線程的中斷狀态将被設定并且該線程将收到一個 ClosedByInterruptException。這時候處理方法一樣,隻是捕獲的異常不一樣而已。

其實對于這些情況有一個通用的處理方法:

  1. package com.polaris.thread; 
  2. public class TestThread2 implements Runnable{ 
  3.     boolean stop = false; 
  4.     public static void main(String[] args) throws Exception { 
  5.         Thread thread = new Thread(new TestThread2(),"My Thread2"); 
  6.         System.out.println( "Starting thread..." ); 
  7.         thread.start(); 
  8.         Thread.sleep( 3000 ); 
  9.         System.out.println( "Interrupting thread..." ); 
  10.         thread.interrupt(); 
  11.         System.out.println("線程是否中斷:" + thread.isInterrupted()); 
  12.         Thread.sleep( 3000 ); 
  13.         System.out.println("Stopping application..." ); 
  14.     } 
  15.     public void run() { 
  16.         while(!stop){ 
  17.             System.out.println( "My Thread is running..." ); 
  18.             // 讓該循環持續一段時間,使上面的話列印次數少點 
  19.             long time = System.currentTimeMillis(); 
  20.             while((System.currentTimeMillis()-time < 1000)) { 
  21.             } 
  22.             if(Thread.currentThread().isInterrupted()) { 
  23.                 return; 
  24.             } 
  25.         } 
  26.         System.out.println("My Thread exiting under request..." ); 
  27.     } 

因為調用interrupt方法後,會設定線程的中斷狀态,是以,通過監視該狀态來達到終止線程的目的。

總結:程式應該對線程中斷作出恰當的響應。響應方式通常有三種:(來自溫紹錦(昵稱:溫少):http//www.cnblogs.com/jobs/)

中斷線程詳解(Interrupt)

注意:interrupted與isInterrupted方法的差別(見API文檔)

引用一篇文章

來自随心所欲http://redisliu.blog.sohu.com/131647795.html 的《Java的interrupt機制》

當外部線程對某線程調用了thread.interrupt()方法後,java語言的處理機制如下:

如果該線程處在可中斷狀态下,(調用了xx.wait(),或者Selector.select(),Thread.sleep()等特定會發生阻塞的api),那麼該線程會立即被喚醒,同時會受到一個InterruptedException,同時,如果是阻塞在io上,對應的資源會被關閉。如果該線程接下來不執行“Thread.interrupted()方法(不是interrupt),那麼該線程處理任何io資源的時候,都會導緻這些資源關閉。當然,解決的辦法就是調用一下interrupted(),不過這裡需要程式員自行根據代碼的邏輯來設定,根據自己的需求确認是否可以直接忽略該中斷,還是應該馬上退出。

如果該線程處在不可中斷狀态下,就是沒有調用上述api,那麼java隻是設定一下該線程的interrupt狀态,其他事情都不會發生,如果該線程之後會調用行數阻塞API,那到時候線程會馬會上跳出,并抛出InterruptedException,接下來的事情就跟第一種狀況一緻了。如果不會調用阻塞API,那麼這個線程就會一直執行下去。除非你就是要實作這樣的線程,一般高性能的代碼中肯定會有wait(),yield()之類出讓cpu的函數,不會發生後者的情況。