本文章將要介紹的內容有如下幾點,讀者朋友也可先自行思考一下相關問題:dom
線程中和中斷相關的方法有三個,分別介紹以下:ide
1) interruptthis
咱們通常都說這個方法是用來中斷線程的,那麼這個中斷應該怎麼理解呢? 就是說把當前正在執行的線程中斷掉,不讓它繼續往下執行嗎?線程
其實,否則。 此處,說的中斷僅僅是給線程設置一箇中斷的標識(設置爲true),線程仍是會繼續往下執行的。而線程怎麼中止,則須要由咱們本身去處理。 一下子會用代碼來講明這個。code
2) isInterrupted對象
判斷當前線程的中斷狀態,即判斷線程的中斷標識是true仍是false。 注意,這個方法不會對線程本來的中斷狀態產生任何影響。blog
3) interrupted資源
也是判斷線程的中斷狀態的。可是,須要注意的是,這個方法和 isInterrupted 有很大的不一樣。咱們看下它們的源碼:get
public boolean isInterrupted() { return isInterrupted(false); } public static boolean interrupted() { return currentThread().isInterrupted(true); } //調用同一個方法,只是傳參不一樣 private native boolean isInterrupted(boolean ClearInterrupted);
首先 isInterrupted 方法是線程對象的方法,而 interrupted 是Thread類的靜態方法。同步
其次,它們都調用了同一個本地方法 isInterrupted,不一樣的只是傳參的值,這個參數表明的是,是否要把線程的中斷狀態清除(清除即不論以前的中斷狀態是什麼值,最終都會設置爲false)。
所以,interrupted 靜態方法會把本來線程的中斷狀態清除,而 isInterrupted 則不會。因此,若是你調用兩次 interrupted 方法,第二次就必定會返回false,除非中間又被中斷了一次。
下面證實一下 interrupt 方法只是設置一箇中斷狀態,而不是使當前線程中斷運行:
public class TestFlag { static volatile boolean flag = true; public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable(){ @Override public void run() { System.out.println("線程中斷標誌:"+Thread.currentThread().isInterrupted()); while (flag){ } System.out.println("標誌flag爲:" + flag); System.out.println("線程中斷標誌:"+Thread.currentThread().isInterrupted()); System.out.println("我還在繼續執行"); } }); t.start(); Thread.sleep(100); flag = false; t.interrupt(); } }
運行結果:
線程中斷標誌:false 標誌flag爲:false 線程中斷標誌:true 我還在繼續執行
當線程啓動,還沒調用中斷方法時,中斷狀態爲false,而後調用中斷方法,並把flag設置爲false。此時,run方法跳出while死循環。咱們會發現線程的中斷狀態爲true,可是線程仍是會繼續往下執行,直到執行結束。
線程中經常使用的阻塞方法,如sleep,join和wait 都會響應中斷,而後拋出一箇中斷異常 InterruptedException。可是,注意此時,線程的中斷狀態會被清除。因此,當咱們捕獲到中斷異常以後,應該保留中斷信息,以便讓上層代碼知道當前線程中斷了。一般有兩種方法能夠作到。
一種是,捕獲異常以後,再從新拋出異常,讓上層代碼知道。另外一種是,在捕獲異常時,經過 interrupt 方法把中斷狀態從新設置爲true。
下面,就以sleep方法爲例,捕獲中斷異常,而後從新設置中斷狀態:
public class TestInterrupt { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new Runnable() { private int count = 0; @Override public void run() { try { count = new Random().nextInt(1000); count = count * count; System.out.println("count:"+count); Thread.sleep(5000); } catch (Exception e) { System.out.println(Thread.currentThread().getName()+"線程第一次中斷標誌:"+Thread.currentThread().isInterrupted()); //從新把線程中斷狀態設置爲true,以便上層代碼判斷 Thread.currentThread().interrupt(); System.out.println(Thread.currentThread().getName()+"線程第二次中斷標誌:"+Thread.currentThread().isInterrupted()); } } }); t.start(); Thread.sleep(100); t.interrupt(); } }
結果:
count:208849 Thread-0線程第一次中斷標誌:false Thread-0線程第二次中斷標誌:true
LockSupport 方法中重要的兩個方法就是park 和 unpark 。
park和interrupt中斷
park方法能夠阻塞當前線程,若是調用unpark方法或者中斷當前線程,則會從park方法中返回。
park方法對中斷方法的響應和 sleep 有一些不太同樣。它不會拋出中斷異常,而是從park方法直接返回,不影響線程的繼續執行。咱們看下代碼:
public class LockSupportTest { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new ParkThread()); t.start(); Thread.sleep(100); //① System.out.println(Thread.currentThread().getName()+"開始喚醒阻塞線程"); t.interrupt(); System.out.println(Thread.currentThread().getName()+"結束喚醒"); } } class ParkThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"開始阻塞"); LockSupport.park(); System.out.println(Thread.currentThread().getName()+"第一次結束阻塞"); LockSupport.park(); System.out.println("第二次結束阻塞"); } }
打印結果以下:
Thread-0開始阻塞 main開始喚醒阻塞線程 main結束喚醒 Thread-0第一次結束阻塞 第二次結束阻塞
當調用interrupt方法時,會把中斷狀態設置爲true,而後park方法會去判斷中斷狀態,若是爲true,就直接返回,而後往下繼續執行,並不會拋出異常。注意,這裏並不會清除中斷標誌。
unpark
unpark會喚醒被park的指定線程。可是,這裏要說明的是,unpark 並非簡單的直接去喚醒被park的線程。看下JDK的解釋:
unpark只是給當前線程設置一個許可證。若是當前線程已經被阻塞了(即調用了park),則會轉爲不阻塞的狀態。如若否則,下次調用park方法的時候也會保證不阻塞。這句話的意思,實際上是指,park和unpark的調用順序無所謂,只要unpark設置了這個許可證,park方法就能夠在任意時刻消費許可證,從而不會阻塞方法。
還須要注意的是,許可證最多隻有一個,也就是說,就算unpark方法調用屢次,也不會增長許可證。 咱們能夠經過代碼驗證,只須要把上邊代碼修改一行便可:
//LockSupportTest類 //原代碼 t.interrupt(); //修改成 LockSupport.unpark(t); LockSupport.unpark(t);
就會發現,只有第一次阻塞會被喚醒,可是第二次依然會繼續阻塞。結果以下:
Thread-0開始阻塞 main開始喚醒阻塞線程 main結束喚醒 Thread-0第一次結束阻塞
另外,在此基礎上,把主線程的sleep方法去掉(代碼中①處),讓主線程先運行,也就是有可能先調用unpark方法,而後子線程纔開始調用park方法阻塞。咱們會發現,出現如下結果,證實了上邊我說的park方法和unpark不分前後順序,park方法能夠隨時消費許可證。
main開始喚醒阻塞線程 main結束喚醒 Thread-0開始阻塞 Thread-0第一次結束阻塞
瞭解了 park/unpark的用法以後,想必你也能分析出來它們和 wait、notify有什麼不一樣之處了。
1) wait和notify方法必須和同步鎖 synchronized一起使用。而park/unpark使用就比較靈活了,沒有這個限制,能夠在任何地方使用。
2) park/unpark 使用時沒有前後順序,均可以使線程不阻塞(前面代碼已驗證)。而wait必須在notify前先使用,若是先notify,再wait,則線程會一直等待。
3) notify只能隨機釋放一個線程,並不能指定某個特定線程,notifyAll是釋放鎖對象中的全部線程。而unpark方法能夠喚醒指定的線程。
4) 調用wait方法會使當前線程釋放鎖資源,但使用的前提是必須已經得到了鎖。 而park不會釋放鎖資源。(如下代碼驗證)
public class LockSyncTest { private static Object lock = new Object(); //保存調用park的線程,以便後續喚醒 private static Thread parkedThread; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ synchronized (lock){ System.out.println("unpark前"); LockSupport.unpark(parkedThread); System.out.println("unpark後"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { //和t1線程用同一把鎖時,park不會釋放鎖資源,若換成this鎖,則會釋放鎖 synchronized (lock){ System.out.println("park前"); parkedThread = Thread.currentThread(); LockSupport.park(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("park後"); } } }); t2.start(); Thread.sleep(100); t1.start(); } } //打印結果 //park前
以上代碼,會一直卡在t2線程,由於park不會釋放鎖,所以t1也沒法執行。
若是把t2的鎖換成this鎖,即只要和t1不是同一把鎖,則t1就會正常執行,而後把t2線程喚醒。打印結果以下:
park前 unpark前 unpark後 park後