引言java
相信各位道友在平時工做中已經不多直接用到Thread線程類了,如今大可能是經過線程池或者一些多線程框架來操做線程任務,但我以爲仍是有必要了解清楚Thread線程類中各類方法的含義,瞭解了底層才能更好的理解框架、應用框架。下面我就將Thread線程的相關基礎點總結一二,以供觀瞻。面試
正文多線程
一、Thread線程的狀態框架
根據《深刻理解Java虛擬機》一書的講述,Java語言定義了五種線程狀態,分別爲:建立(new)、運行(Runnable)、等待(waiting)、阻塞(blocked)、結束(terminated)。並且規定,在某一個時間點,每一個線程能且只能處於其中的一種狀態。ide
其中,運行狀態又包括就緒(Ready)跟正在運行(Running),區別就是是否得到了CPU的執行時間。學習
對於等待跟阻塞狀態,須要着重說明一下,由於此處極易搞錯,並且也是面試常被問到的點。等待狀態,通常由Object.wait()、Thread.sleep()、Thread.join()、LockSupport.park()等方法以及這些方法帶時間控制的同類方法實現線程的等待。而阻塞狀態,通常是因爲當前線程還未獲取到獨佔鎖且正在等待獲取,此時稱爲阻塞。能夠將等待看作主動的線程暫停執行,覺得須要調用特定的方法線程纔會等待;而阻塞能夠看作是被動的線程暫定執行,由於線程在等着獲取獨佔鎖。spa
二、Thread線程的相關方法線程
start()方法/run()方法:有時在面試的時候,面試官會問到調用線程的start方法跟直接調用run方法有什麼區別?雖然有的道友看到這裏會以爲問這種問題的面試官有點很不必,但我仍是說一下。調用start方法後,最終會調用Thread類中的一個本地方法start0,這個方法能夠新建一個線程來運行你的run方法,而調用run方法後只是在當前線程上運行你的run方法,並無新線程參與。code
wait()方法/sleep()方法:請注意,這裏不少人都會記錯,wait方法以及跟它配套的notify/notifyAll方法,是位於頂級父類Object下的,而其餘操做線程的方法都在Thread線程類下。爲何要將wait方法放在Object下呢?其實這是由wait/notify方法的實現原理決定的。wait方法調用了以後,會釋放鎖,並讓當前線程等待,而對於java的原生鎖synchronized,是隸屬於一個特定對象的監視器monitor的,那這個釋放的是鎖誰的鎖?不能是別人的,只能是調用wait方法的那個對象的。而這個鎖是哪裏來的?要釋放鎖,確定以前加過鎖,在哪裏加的呢?只能是在synchronized塊中給這個對象加的,因此這也解釋了爲何wait/notify方法一直要跟synchronized一塊兒用,由於它倆就是經過操做對象的鎖實現的等待和喚醒。相比而言sleep方法單純不少,它只是讓當前線程睡眠一段時間,並不會涉及到對鎖的操做,因此直接放在Thread類中就行。對於wait跟notify的演示以下:對象
1 public static void main(String[] args) throws InterruptedException { 2 Object obj = new Object(); 3 Thread thread = new Thread(new Runnable() { 4 @Override 5 public void run() { 6 synchronized (obj) { 7 try { 8 System.out.println("thread獲取到鎖,觸發wait"); 9 obj.wait(); 10 System.out.println("wait over"); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 } 16 }); 17 Thread thread1 = new Thread(new Runnable() { 18 @Override 19 public void run() { 20 synchronized (obj) { 21 try { 22 System.out.println("thread1獲取到鎖"); 23 Thread.sleep(1000); 24 System.out.println("1秒後喚醒"); 25 obj.notify(); 26 } catch (Exception e) { 27 e.printStackTrace(); 28 } 29 System.out.println("notify over"); 30 } 31 32 } 33 }); 34 thread.start(); 35 thread1.start(); 36 }
執行結果爲:
thread獲取到鎖,觸發wait
thread1獲取到鎖
1秒後喚醒
notify over
wait over
LockSupport.park():另外還有JUC包中的park方法讓當前線程等待。此方法是使用CAS實現的線程等待,不會釋放鎖。而park/unpark方法比wait/notify這一對好的地方在於,前者能夠先unpark在park,這是線程仍然會繼續執行;而對於wait/notify,則須要經過程序控制執行順序,必定要先wait在notify/notifyAll,不然順序反了線程就會一直等待下去,由此悲劇誕生... 好比講上述wait/notify的代碼34行35行調換一下順序,執行結果以下所示:
thread1獲取到鎖
1秒後喚醒
notify over
thread獲取到鎖,觸發wait
彷彿雲天明對程心那一千八百萬年的等待
join()/yield():對於Thread下的這兩個方法,之因此放在一塊兒講解,就是由於這兩個方法平時比較少用到,屬於閒雲野鶴的存在。
yield()方法是讓當前線程讓步,讓步的意思就是放棄執行權,即當前線程會從上述說的運行狀態runnable中的running狀態進入ready就緒狀態,可是虛擬機不保證當前線程執行了yield方法後不會緊接着再次進去running狀態,由於可能CPU分配執行時間時又分給了當前線程。因此這個方法其實通常也沒啥用,由於效果不穩定。
join()方法是將調用join的線程插入當前線程的執行過程當中,即讓當前線程等待,先執行完調用join的線程,再繼續執行當前線程。注意join方法不會釋放鎖。join的演示代碼以下:
1 public class RunnableThread implements Runnable{ 2 @Override 3 public void run() { 4 System.out.println("runnable run"); 5 try { 6 System.out.println("開始睡眠"); 7 Thread.sleep(5000); 8 System.out.println("睡了5秒"); 9 } catch (Exception e) { 10 System.out.println("runnable exception:" + e); 11 } 12 } 13 14 public static void main(String[] args) throws InterruptedException { 15 Object obj = new Object(); 16 Thread thread = new Thread(new RunnableThread()); 17 thread.start(); 18 thread.join(); 19 System.out.println("end"); 20 } 21 }
執行結果爲:
runnable run
開始睡眠
睡了5秒
end
結束語
此次先到這裏,上述說的東西,雖然很小,並且實際中不會直接用到,可是對於咱們理解線程的運行機制、理解多線程框架都有好處,因此仍是有必要在本身的學習地圖上理解清楚。其實線程還有一個很重要的點就是線程的中斷,多線程框架或者JUC包的源碼中都會涉及到對線程中斷的處理以及響應,這一塊我會在後面梳理清楚了以後專門整理出來。最近以爲學習進入了停滯期,有點不知道從何下手,以爲須要學的東西太多。在這裏,想跟各位道友討教一下,一個資質普通的開發者,如何才能將本身的實力提高到一個比較高的層次(好比阿里的P6P7及以上?)歡迎留言賜教,在此不勝感激!