線程從建立到銷燬通常分爲五種狀態,以下圖:面試
1) 新建segmentfault
當用new關鍵字建立一個線程時,就是新建狀態。多線程
2) 就緒ide
調用了 start 方法以後,線程就進入了就緒階段。此時,線程不會當即執行run方法,須要等待獲取CPU資源。學習
3) 運行this
當線程得到CPU時間片後,就會進入運行狀態,開始執行run方法。spa
4) 阻塞線程
當遇到如下幾種狀況,線程會從運行狀態進入到阻塞狀態。3d
須要注意的是,阻塞狀態只能進入就緒狀態,不能直接進入運行狀態。由於,從就緒狀態到運行狀態的切換是不受線程本身控制的,而是由線程調度器所決定。只有當線程得到了CPU時間片以後,纔會進入運行狀態。code
5) 死亡
當run方法正常執行結束時,或者因爲某種緣由拋出異常都會使線程進入死亡狀態。另外,直接調用stop方法也會中止線程。可是,此方法已經被棄用,不推薦使用。
1)sleep
當調用 Thread.sleep(long millis) 睡眠方法時,就會使當前線程進入阻塞狀態。millis參數指定了線程睡眠的時間,單位是毫秒。 當時間結束以後,線程會從新進入就緒狀態。
注意,若是當前線程得到了一把同步鎖,則 sleep方法阻塞期間,是不會釋放鎖的。
2) wait、notify和notifyAll
首先,它們都是Object類中的方法。須要配合 Synchronized關鍵字來使用。
調用線程的wait方法會使當前線程等待,直到其它線程調用此對象的notify/notifyAll方法。 若是,當前對象鎖有N個線程在等待,則notify方法會隨機喚醒其中一個線程,而notifyAll會喚醒對象鎖中全部的線程。須要注意,喚醒時,不會立馬釋放鎖,只有當前線程執行完以後,纔會把鎖釋放。
另外,wait方法和sleep方法不一樣之處,在於sleep方法不會釋放鎖,而wait方法會釋放鎖。wait、notify的使用以下:
public class WaitTest { private static Object obj = new Object(); public static void main(String[] args) throws InterruptedException { ListAdd listAdd = new ListAdd(); Thread t1 = new Thread(() -> { synchronized (obj){ try { for (int i = 0; i < 10; i++) { listAdd.add(); System.out.println("當前線程:"+Thread.currentThread().getName()+"添加了一個元素"); Thread.sleep(300); if(listAdd.getSize() == 5){ System.out.println("發出通知"); obj.notify(); } } } catch(InterruptedException e){ e.printStackTrace(); } } }); Thread t2 = new Thread(() -> { synchronized (obj){ try { if(listAdd.getSize() != 5){ obj.wait(); } } catch(InterruptedException e){ e.printStackTrace(); } System.out.println("線程:"+Thread.currentThread().getName()+"被通知."); } }); t2.start(); Thread.sleep(1000); t1.start(); } } class ListAdd { private static volatile List<String> list = new ArrayList<String>(); public void add() { list.add("abc"); } public int getSize() { return list.size(); } }
以上,就是建立一個t2線程,判斷list長度是否爲5,不是的話,就一直阻塞。而後,另一個t1線程不停的向list中添加元素,當元素長度爲5的時候,就去喚醒阻塞中的t2線程。
然而,咱們會發現,此時的t1線程會繼續往下執行。直到方法執行完畢,纔會把鎖釋放。t1線程去喚醒t2的時候,只是讓t2具備參與鎖競爭的資格。只有t2真正得到了鎖以後纔會繼續往下執行。
3) join
當線程調用另一個線程的join方法時,當前線程就會進入阻塞狀態。直到另一個線程執行完畢,當前線程纔會由阻塞狀態轉爲就緒狀態。
或許,你在面試中,會被問到,怎麼才能保證t1,t2,t3線程按順序執行呢。(由於,咱們知道,正常狀況下,調用start方法以後,是不能控制線程的執行順序的)
咳咳,當前青澀的我,面試時就被問到這個問題,是一臉懵逼。其實,是很是簡單的,用join方法就能夠輕鬆實現:
public class TestJoin { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new MultiT("a")); Thread t2 = new Thread(new MultiT("b")); Thread t3 = new Thread(new MultiT("c")); t1.start(); t1.join(); t2.start(); t2.join(); t3.start(); t3.join(); } } class MultiT implements Runnable{ private String s; private int i; public MultiT(String s){ this.s = s; } @Override public void run() { while(i<10){ System.out.println(s+"===="+i++); } } }
最終,咱們會看到,線程會按照t1,t2,t3順序執行。由於,主線程main總會等調用join方法的那個線程執行完以後,纔會往下執行。
4) yield
Thread.yield 方法會使當前線程放棄CPU時間片,把執行機會讓給相同或更高優先級的線程(yield英文意思就是屈服,放棄的意思嘛,能夠理解爲當前線程暫時屈服於別人了)。
注意,此時當前線程不會阻塞,只是進入了就緒狀態,隨時能夠再次得到CPU時間片,從而進入運行狀態。也就是說,其實yield方法,並不能保證,其它相同或更高優先級的線程必定會得到執行權,也有可能,再次被當前線程拿到執行權。
yield方法和sleep方法同樣,也是不釋放鎖資源的。能夠經過代碼來驗證這一點:
public class TestYield { public static void main(String[] args) { YieldThread yieldThread = new YieldThread(); for (int i = 0; i < 10; i++) { Thread t = new Thread(yieldThread); t.start(); } } } class YieldThread implements Runnable { private int count = 0; @Override public synchronized void run() { for (int i = 0; i < 10; i++) { count ++; if(count == 1){ Thread.yield(); System.out.println("線程:"+Thread.currentThread().getName() + "讓步"); } System.out.println("線程:"+Thread.currentThread().getName() + ",count:"+count); } } }
結果:
會看到,線程讓步以後,並不會釋放鎖。所以,其它線程也沒機會得到鎖,只能把當前線程執行完以後,纔會釋放。(對於這一點,其實我是有疑問的。既然yield不釋放鎖,那爲何還要放棄執行權呢。就算放棄了執行權,別的線程也沒法得到鎖啊。)
因此,個人理解,yield通常用於不存在鎖競爭的多線程環境中。若是當前線程執行的任務時間可能比較長,就能夠選擇用yield方法,暫時讓出CPU執行權。讓其它線程也有機會執行任務,而不至於讓CPU資源一直消耗在當前線程。
5)suspend、resume
suspend 會使線程掛起,而且不會自動恢復,只有調用 resume 方法才能使線程進入就緒狀態。注意,這兩個方法因爲有可能致使死鎖,已經被廢棄。
若是本文對你有用,歡迎點贊,評論,轉發。
學習是枯燥的,也是有趣的。我是「煙雨星空」,歡迎關注,可第一時間接收文章推送。