根據Java Doc的定義
Thrown when a thread is waiting, sleeping, or otherwise occupied, and the thread is interrupted, either before or during the activity. Occasionally a method may wish to test whether the current thread has been interrupted, and if so, to immediately throw this exception.
意思是說當一個線程處於等待,睡眠,或者佔用,也就是說阻塞狀態,而這時線程被中斷就會拋出這類錯誤,但是線程並沒有被中斷,任何時候,出現 該異常都需要手動在代碼中再次中斷該異常。Java6之後結束某個線程A的方法是A.interrupt()。如果這個線程正處於非阻塞狀態,比如說線程正在執行某些代碼的時候,不過被interrupt,那麼該線程的interrupt變量會被置爲true,告訴別人說這個線程被中斷了(只是一個標誌位,這個變量本身並不影響線程的中斷與否),而且線程會被中斷,這時不會有interruptedException。但如果這時線程被阻塞了,比如說正在睡眠,那麼就會拋出這個錯誤。請注意,這個時候變量interrupt沒有被置爲true,而且也沒有人來中斷這個線程。這類阻塞操作中中斷線程,需要做如下操作。
在java的中斷機制中, InterruptedException異常佔據重要的位置. 處理InterruptedException異常的方式有:
1. 不catch直接向上層拋出, 或者catch住做一些清理工作之後重拋該異常. 這樣的處理使得你的方法也成爲一個可中斷的阻塞方法:
- // 直接向上層拋出InterruptedException, dosomething方法也是一個可中斷的阻塞方法
- private void dosomething() throws InterruptedException {
- Thread.sleep(1000);
- }
2. 有時不能向上拋出InterruptedException異常(例如父類的相應方法沒有聲明該異常), 此時catch之後, 必須設置當前線程的中斷標記爲true, 以表明當前線程發生了中斷, 以便調用棧上層進行處理:
- public class InterruptedExceptionHandler implements Runnable {
- private Object lock = new Object();
- @Override
- public void run() {
- while (!Thread.currentThread().isInterrupted()) {
- dosomething();
- }
- }
- private void dosomething() {
- try {
- // Object.wait是一個可中斷的阻塞方法, 如果在其阻塞期間檢查到當前線程的中斷標記爲true, 會重置中斷標記後從阻塞狀態返回, 並拋出InterruptedException異常
- synchronized (lock) {
- lock.wait();
- }
- } catch (InterruptedException e) {
- System.out.println("InterruptedException happened");
- // catch住InterruptedException後設置當前線程的中斷標記爲true, 以供調用棧上層進行相應的處理
- // 在此例中, dosomething方法的調用棧上層是run方法.
- Thread.currentThread().interrupt();
- }
- }
- public static void main(String[] args) throws InterruptedException {
- Thread t = new Thread(new InterruptedExceptionHandler());
- t.start();
- // 啓動線程1s後設置其中斷標記爲true
- Thread.sleep(1000);
- t.interrupt();
- }
- }
主線程啓動InterruptedExceptionHandler線程1s後, 設置InterruptedExceptionHandler線程的中斷標記爲true. 此時InterruptedExceptionHandler線程應該阻塞在wait方法上, 由於wait方法是可中斷的阻塞方法, 所以其檢查到中斷標記爲true時, 將重置當前線程的中斷標記後拋出InterruptedException, dosomething方法catch住InterruptedException異常後, 再次將當前線程的中斷標記設置爲true, run方法檢查到中斷標記爲true, 循環不再繼續. 假如dosomething方法catch住InterruptedException異常後沒有設置中斷標記, 其調用棧上層的run方法就無法得知線程曾經發生過中斷, 循環也就無法終止.
3. 還有一種情形比較特殊: 我們希望發生了InterruptedException異常後仍然繼續循環執行某阻塞方法, 此時應該將中斷狀態保存下來, 當循環完成後再根據保存下來的中斷狀態執行相應的操作:
- public class InterruptedExceptionContinueHandler implements Runnable {
- private BlockingQueue<Integer> queue;
- public InterruptedExceptionContinueHandler(BlockingQueue<Integer> queue) {
- this.queue = queue;
- }
- @Override
- public void run() {
- while (!Thread.currentThread().isInterrupted()) {
- dosomething();
- }
- System.out.println(queue.size());
- }
- private void dosomething() {
- // cancelled變量用於表明線程是否發生過中斷
- boolean cancelled = false;
- for (int i = 0; i < 10000; i++) {
- try {
- queue.put(i);
- } catch (InterruptedException e) {
- // 就算髮生了InterruptedException, 循環也希望繼續運行下去, 此時將cancelled設置爲true, 以表明遍歷過程中發生了中斷
- System.out.println("InterruptedException happened when i = " + i);
- cancelled = true;
- }
- }
- // 如果當前線程曾經發生過中斷, 就將其中斷標記設置爲true, 以通知dosomething方法的上層調用棧
- if (cancelled) {
- Thread.currentThread().interrupt();
- }
- }
- public static void main(String[] args) throws InterruptedException {
- Thread t = new Thread(new InterruptedExceptionContinueHandler(new LinkedBlockingQueue<Integer>()));
- t.start();
- // 啓動線程2ms後設置其中斷標記爲true
- Thread.sleep(2);
- t.interrupt();
- }
- }
在我的機器中, 輸出結果如下:
InterruptedException happened when i = 936
size = 9999
隊列的size是9999而不是10000, 是因爲i = 936時發生了InterruptedException異常, 該次put沒有成功.
爲什麼不在發生InterruptedException時就設置當前線程的中斷標記, 而非要繞一圈? 假設將dosomething方法改爲:
- private void dosomething() {
- for (int i = 0; i < 10000; i++) {
- try {
- queue.put(i);
- } catch (InterruptedException e) {
- System.out.println("InterruptedException happened when i = " + i);
- Thread.currentThread().interrupt();
- }
- }
- }
運行後發現結果類似爲:
InterruptedException happened when i = 936
InterruptedException happened when i = 937
...
InterruptedException happened when i = 9998
InterruptedException happened when i = 9999
size = 936
catch住InterruptedException後立即將當前線程的中斷標記設置爲true, 就會導致put方法又拋出InterruptedException異常, 如此往復直到循環結束.
4. 最不可取的是catch了InterruptedException異常但是不做任何處理, 這樣一來調用棧上層就無法得知當前線程是否發生過中斷. 只有一種情況下可以這樣處理: 當InterruptedException發生在調用棧的最上層, 如run方法, 或者main方法中, 且後續代碼不檢查中斷狀態時:
- public static void main(String[] args) {
- // main方法已經是調用棧的最上層, 此時可以catchInterruptedException後不做任何處理
- try {
- Thread.sleep(2);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }