引言
Java沒有提供任何機制來安全地終止執行緒,但提供了中斷機制,即thread.interrupt()方法。執行緒中斷是一種協作式的機制,並不是說呼叫了中斷方法之後目標執行緒一定會立即中斷,而是傳送了一箇中斷請求給目標執行緒,目標執行緒會自行在某個取消點中斷自己。這種設定很有必要,因為如果不論執行緒執行到何種情況都立即響應中斷的話,很容易造成某些物件狀態不一致的情況出現。
正文
一、中斷相關的方法介紹
涉及到中斷的執行緒基礎方法有三個:interrupt()、isInterrupted()、interrupted(),它們都位於Thread類下。Thread類下還有一個
interrupt()方法:對目標執行緒傳送中斷請求,看其原始碼會發現最終是呼叫了一個本地方法實現的執行緒中斷;
interrupted()方法:返回目標執行緒是否中斷的布林值(通過本地方法實現),且返回後會重置中斷狀態為未中斷;
isInterrupted()方法:該方法返回的是執行緒中斷與否的布林值(通過本地方法實現),不會重置中斷狀態;
二、執行緒中斷
執行緒中斷可以按中斷時執行緒狀態分為兩類,一類是執行時執行緒的中斷,一類是阻塞或等待執行緒的中斷。有中斷時,執行時的執行緒會在某個取消點中斷執行,而處於阻塞或者等待狀態的執行緒大多會立即響應中斷,比如上一篇文章中提到的join、sleep等方法,這些方法在丟擲中斷異常的錯誤後,會重置執行緒中斷狀態為未中斷。但注意,獲取獨佔鎖的阻塞狀態與BIO的阻塞狀態不會響應中斷。而在JUC包中有在加鎖阻塞的過程中響應中斷的方法,比如lockInterruptibly()。
下面從三個問題來講述執行緒中斷
1、執行緒中斷的目的是什麼?
為什麼要進行執行緒中斷?有時是由於對於某種特定情況,我們知道當前執行緒無需繼續執行下去,此時可以中斷此執行緒;有時是遇到某些異常,需要中斷執行緒。具體什麼目的,還要看具體場景,但執行緒中斷的需求已經擺在那裡,肯定需要。
2、要如何處理執行緒中斷?
通常的處理方式有兩種,如果是業務層面的程式碼,則只需要做好中斷執行緒之後的業務邏輯處理即可,而如果是偏底層功能的執行緒中斷,則儘量將中斷異常丟擲(或者在catch中重新呼叫interrupt()來中斷執行緒),以告知上層方法本執行緒的中斷經歷。
3、JUC中對中斷的處理舉例
JUC中ReentrantLock常用的加鎖方法是lock(),還有一個響應中斷的加鎖方法lockInterruptibly()
lock()方法中的acquire(int arg)方法如下所示:
1 public final void acquire(int arg) { 2 if (!tryAcquire(arg) && 3 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 4 selfInterrupt(); 5 }
在acquireQueued中會對執行緒的中斷狀態做判斷,如果中斷了則返回true,進入selfInterrupt()方法,恢復執行緒的中斷狀態。但注意此處是在獲取到鎖之後再響應中斷,在獲取到鎖之前不會做出響應。
1 static void selfInterrupt() { 2 Thread.currentThread().interrupt(); 3 }
而看lockInterruptibly()方法:
1 public void lockInterruptibly() throws InterruptedException { 2 sync.acquireInterruptibly(1); 3 } 4 5 public final void acquireInterruptibly(int arg) 6 throws InterruptedException { 7 if (Thread.interrupted()) 8 throw new InterruptedException(); 9 if (!tryAcquire(arg)) 10 doAcquireInterruptibly(arg); 11 }
它會先檢視中斷狀態,再獲取鎖。而如果在獲取鎖的過程中中斷過,則會在doAcquireInterruptibly方法中丟擲中斷異常。
下面是我在本地模擬的lock阻塞中斷:
1 public class ReentrantLockDemo { 2 public static void main(String[] args) throws InterruptedException { 3 System.out.println("main start"); 4 Thread thread1 = new Thread(new LockThreadDemo()); 5 Thread thread2 = new Thread(new LockThreadDemo()); 6 thread1.start(); 7 Thread.sleep(1000); // 確保thread1獲取到了鎖 8 thread2.start(); // 此時thread2處於獲取鎖的阻塞狀態 9 thread2.interrupt(); 10 System.out.println("main end"); 11 } 12 } 13 14 class LockThreadDemo implements Runnable { 15 public static ReentrantLock lock = new ReentrantLock(); 16 @Override 17 public void run() { 18 System.out.println(Thread.currentThread().getName() + "runnable run"); 19 try { 20 lock.lock(); 21 System.out.println(Thread.currentThread().getName() + "開始睡眠"); 22 Thread.sleep(5000); 23 System.out.println(Thread.currentThread().getName() + "睡了5秒"); 24 } catch (Exception e) { 25 System.out.println(Thread.currentThread().getName() + "runnable exception:" + e); 26 } finally { 27 lock.unlock(); 28 } 29 System.out.println(Thread.currentThread().getName() + " over"); 30 } 31 }
執行結果為:
main start Thread-0runnable run Thread-0開始睡眠 main end Thread-1runnable run Thread-0睡了5秒 Thread-0 over Thread-1開始睡眠 Thread-1runnable exception:java.lang.InterruptedException: sleep interrupted Thread-1 over
可以看到中斷了並沒有對獲取鎖產生影響,最後是sleep方法響應的中斷。
下面是我在本地模擬的lockInterruptibly()阻塞中斷:
1 public class ReentrantLockInterruptableDemo { 2 public static void main(String[] args) throws InterruptedException { 3 System.out.println("main start"); 4 Thread thread1 = new Thread(new LockThreadInterruptableDemo()); 5 Thread thread2 = new Thread(new LockThreadInterruptableDemo()); 6 thread1.start(); 7 Thread.sleep(1000); // 確保thread1獲取到了鎖 8 thread2.start(); // 此時thread2處於獲取鎖的阻塞狀態 9 thread2.interrupt(); 10 System.out.println("main end"); 11 } 12 } 13 14 class LockThreadInterruptableDemo implements Runnable { 15 public static ReentrantLock lock = new ReentrantLock(); 16 @Override 17 public void run() { 18 System.out.println(Thread.currentThread().getName() + "runnable run"); 19 try { 20 lock.lockInterruptibly(); 21 System.out.println(Thread.currentThread().getName() + "開始睡眠"); 22 Thread.sleep(5000); 23 System.out.println(Thread.currentThread().getName() + "睡了5秒"); 24 } catch (Exception e) { 25 System.out.println(Thread.currentThread().getName() + "runnable exception:" + e); 26 } finally { 27 try { 28 lock.unlock(); 29 } catch (IllegalMonitorStateException e) { 30 System.out.println("因執行緒" + Thread.currentThread().getName() + "提前中斷導致未獲取到鎖"); 31 } 32 } 33 System.out.println(Thread.currentThread().getName() + " over"); 34 } 35 }
結果為:
main start Thread-0runnable run Thread-0開始睡眠 main end Thread-1runnable run Thread-1runnable exception:java.lang.InterruptedException 因執行緒Thread-1提前中斷導致未獲取到鎖 Thread-1 over Thread-0睡了5秒 Thread-0 over
結束語
對於執行緒中斷的處理比較常見,尤其是涉及到多執行緒的框架、元件中。而能否處理好執行緒中斷的各種情況,則體現出一個程式設計師對多執行緒掌握的熟練情況。每天進步一點,日拱一卒,加油!