Thread線程的基礎知識及常見疑惑點

引言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及以上?)歡迎留言賜教,在此不勝感激!

相關文章
相關標籤/搜索