引言java
Java沒有提供任何機制來安全地終止線程,但提供了中斷機制,即thread.interrupt()方法。線程中斷是一種協做式的機制,並非說調用了中斷方法以後目標線程必定會當即中斷,而是發送了一箇中斷請求給目標線程,目標線程會自行在某個取消點中斷本身。這種設定頗有必要,由於若是不論線程執行到何種狀況都當即響應中斷的話,很容易形成某些對象狀態不一致的狀況出現。程序員
正文安全
1、中斷相關的方法介紹多線程
涉及到中斷的線程基礎方法有三個:interrupt()、isInterrupted()、interrupted(),它們都位於Thread類下。Thread類下還有一個框架
interrupt()方法:對目標線程發送中斷請求,看其源碼會發現最終是調用了一個本地方法實現的線程中斷;ide
interrupted()方法:返回目標線程是否中斷的布爾值(經過本地方法實現),且返回後會重置中斷狀態爲未中斷;ui
isInterrupted()方法:該方法返回的是線程中斷與否的布爾值(經過本地方法實現),不會重置中斷狀態;spa
2、線程中斷線程
線程中斷能夠按中斷時線程狀態分爲兩類,一類是運行時線程的中斷,一類是阻塞或等待線程的中斷。有中斷時,運行時的線程會在某個取消點中斷執行,而處於阻塞或者等待狀態的線程大多會當即響應中斷,好比上一篇文章中提到的join、sleep等方法,這些方法在拋出中斷異常的錯誤後,會重置線程中斷狀態爲未中斷。但注意,獲取獨佔鎖的阻塞狀態與BIO的阻塞狀態不會響應中斷。而在JUC包中有在加鎖阻塞的過程當中響應中斷的方法,好比lockInterruptibly()。code
下面從三個問題來說述線程中斷
一、線程中斷的目的是什麼?
爲何要進行線程中斷?有時是因爲對於某種特定狀況,咱們知道當前線程無需繼續執行下去,此時能夠中斷此線程;有時是遇到某些異常,須要中斷線程。具體什麼目的,還要看具體場景,但線程中斷的需求已經擺在那裏,確定須要。
二、要如何處理線程中斷?
一般的處理方式有兩種,若是是業務層面的代碼,則只須要作好中斷線程以後的業務邏輯處理便可,而若是是偏底層功能的線程中斷,則儘可能將中斷異常拋出(或者在catch中從新調用interrupt()來中斷線程),以告知上層方法本線程的中斷經歷。
三、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
結束語
對於線程中斷的處理比較常見,尤爲是涉及到多線程的框架、組件中。而可否處理好線程中斷的各類狀況,則體現出一個程序員對多線程掌握的熟練狀況。天天進步一點,日拱一卒,加油!