本節內容:java
線程一共有六種狀態,分別是New(新建)、Runnable(可運行)、Blocked(阻塞)、Waiting(等待)、Timed WaitIng(計時等待)、Terminated(終結) 設計模式
狀態流轉圖安全
NEW(新建) 多線程
當咱們new一個新線程的時候,若是還未調用start()方法,則該線程的狀態就是NEW,而一旦調用了start()方法,它就會從NEW變成Runnableide
Runnable(可運行)this
java中的可運行狀態分爲兩種,一種是可運行,一種是運行中,若是當前線程調用了start()方法以後,還未獲取CPU時間片,此時該線程處於可運行狀態,等待被分配CPU資源,若是得到CPU資源後,該線程就是運行狀態。spa
Blocked(阻塞) 線程
java中的阻塞也分三種狀態:Blocked(被阻塞)、Waiting(等待)、Timed Waiting(計時等待),這三種狀態統稱爲阻塞狀態。設計
Blocked狀態(被阻塞):從結合圖中能夠看出從Runnable狀態進入Blocked狀態只有進入synchronized保護的代碼時,沒有獲取到鎖monitor鎖,就會處於Blocked狀態code
Time Waiting(計時等待):Time Waiting和Waiting狀態的區別是有沒有時間的限制,一下狀況會進入Time Waiting:
設置了時間參數的Thread.sleep(long millis)
設置了時間參數的Object.wait(long timeout)
設置了時間參數的Thread.join(long millis)
設置了時間參數的LockSupport.parkNanos(long millis)和LockSupport.parkUntil(long deadline)
Waiting狀態(等待):線程進入Waiting狀態有三種狀況,分別是:
沒有設置Timeout的Object.wait()方法
沒有設置Timeout的Thread.join()方法
Blocked狀態僅僅針對synchronized monitor鎖,若是獲取的鎖是ReentrantLock等鎖時,線程沒有搶到鎖就會進入Waiting狀態,由於本質上它執行的是LockSupport.park()方法,因此會進入Waiting方法,一樣Object.wait()、Thread.join()也會讓線程進入waiting狀態。Blocked和Waiting不一樣的是blocked等待其餘線程釋放monitor鎖,而Waiting則是等待某個條件,相似join線程執行完畢或者notify()\notifyAll()。
上圖中能夠看出處於Waiting、Time Waiting的線程調用notify()或者notifyAll()方法後,並不會進入Runnable狀態而是進入Blocked狀態,由於喚醒處於Waiting、Time Waiting狀態的線程的線程在調用notify()或者notifyAll()時候,必須持有該monitor鎖,因此處於Waiting、Time Waiting狀態的線程被喚醒後,就會進入Blocked狀態,直到執行了notify()\notifyAll()的線程釋放了鎖,被喚醒的線程才能夠去搶奪這把鎖,若是搶到了就從Blocked狀態轉換到Runnable狀態
Terminated(終結)
進入這個狀態的線程分兩種狀況:
run()方法執行完畢,正常退出
首先wait方法必須在sychronized保護的同步代碼中使用,在wait方法的源碼註釋中就有說:
在使用wait方法是必須把wait方法寫在synchronized保護的while代碼中,而且始終判斷執行條件是否知足,若是知足就繼續往下執行,不知足就執行wait方法,並且執行wait方法前,必須先持有對象的synchronized鎖.
上面主要是兩點:
wait方法要在synchronized同步代碼中調用.
咱們先分析第一點,結合如下場景分析爲何要這麼設計
public class TestDemo { private ArrayBlockingQueue<String> storage = new ArrayBlockingQueue(8); public void add(String data){ storage.add(data); notify(); } public String remove() throws InterruptedException { //wait不用synchronized關鍵字保護,直接調用, while (storage.isEmpty()){ wait(); } return storage.remove(); } }
上述代碼是一個簡單的基於ArrayBlockingQueue實現的生產者、消費者模式,生產者調用add(String data)方法向storage中添加數據,消費者調用remove()方法從storage中消費數據.
代碼中咱們能夠看到若是wait方法的調用沒有用synchronized保護起來,那麼就可能發生一下場景狀況:
消費者線程調用remove()方法判斷storage是否爲空,若是是就調用wait方法,消費者線程進入等待,可是這就可能發生消費者線程調用完storage.isEmpty()方法後就被調度器暫停了,而後還沒來得及執行wait方法.
此時生產者線程開始運行,開始執行了add(data)方法,成功的添加了data數據而且執行了notify()方法,可是由於以前的消費者尚未執行wait方法,因此此時沒有線程被喚醒.
以上的狀況就是線程不安全的,由於wait方法的調用錯過了notify方法的喚醒,致使應該被喚醒的線程沒法收到notify方法的喚醒.
正是由於wait方法的調用沒有被synchronized關鍵字保護,因此他和while判斷不是原子操做,因此就會出現線程安全問題.
咱們把以上代碼改爲以下,就實現了線程安全
public class TestDemo { private ArrayBlockingQueue<String> storage = new ArrayBlockingQueue(8); public void add(String data){ synchronized (this){ storage.add(data); notify(); } } public String remove() throws InterruptedException { synchronized (this){ while (storage.isEmpty()){ wait(); } return storage.remove(); } } }
咱們再來分析第二點wait方法應該老是被調用在一個循環中?
之因此將wait方法放到循環中是爲了防止線程「虛假喚醒「(spurious wakeup),線程可能在沒有被notify/notyfiAll,也沒有被中斷或者超時的狀況下被喚醒,雖然這種機率發生很是小,可是爲了保證發生虛假喚醒的正確性,因此須要採用循環結構,這樣即使線程被虛假喚醒了,也會再次檢查while的條件是否知足,不知足就調用wait方法等待.
爲何wait/notify/notifyAll被定義在Object類中
java中每一個對象都是一個內置鎖,都持有一把稱爲monitor監視器的鎖,這就要求在對象頭中有一個用來保存鎖信息的位置.這個鎖是對象級別的而非線程級別的,wait/notify/notifyAll也都是鎖級別的操做,它們的鎖屬於對象,因此把它們定義在Object中最合適.
wait/notify和sleep方法的異同
相同點:
它們均可以讓線程阻塞
不一樣點:
wait方法必須在synchronized同步代碼中調用,sleep方法沒有這個要求
調用sleep不會釋放monitor鎖,調用wait方法就釋放monitor鎖
sleep要求等待一段時間後會自動恢復,可是wait方法沒有設置超時時間的話會一直等待,直到被中斷或者被喚醒,不然不能主動恢復
正確的中止線程方式是經過使用interrupt方法,interrupt方法僅僅起到了通知須要被中斷的線程的做用,被中斷的線程有徹底的自主權,它能夠馬上中止,也能夠執行一段時間再中止,或者壓根不中止.這是由於java但願程序之間能互相通知、協做的完成任務.
interrupt()方法的使用
public class InterruptDemo implements Runnable{ public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new InterruptDemo()); thread.start(); Thread.sleep(5); thread.interrupt(); } @Override public void run() { int i =0; while (!Thread.currentThread().isInterrupted() && i<1000){ System.out.println(i++); } } }
上圖中經過循環打印0~999,可是實際運行並不會打印到999,由於在線程打印到999以前,咱們對線程調用了interrupt方法使其中斷了,而後根據while中的判斷條件,方法提早終止,運行結果以下:
其中若是是經過sleep、wait方法使線程陷入休眠,處於休眠期間的線程若是被中斷是能夠感覺到中斷信號的,而且會拋出一個InterruptException異常,同時清除中斷信號,將中斷標記位設置爲false.
生產者消費者模式是程序設計中常見的一種設計模式,咱們經過下圖來理解生產者消費者模式:
使用BolckingQueue實現生產者消費者模式
經過利用阻塞隊列ArrayBlockingQueue實現一個簡單的生產者消費者模式,建立兩個線程用來生產對象,兩個線程用來消費對象,若是ArrayBlockingQueue滿了,那麼生產者就會阻塞,若是ArrayBlockingQueue爲空,那麼消費者線程就會阻塞.線程的阻塞和喚醒都是經過ArrayBlockingQueue來完成的.
public void MyBlockingQueue1(){ BlockingQueue<Object> queue=new ArrayBlockingQueue<>(10); Runnable producer = () ->{ while (true){ try { queue.put(new Object()); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(producer).start(); new Thread(producer).start(); Runnable consumer = () ->{ while (true){ try { queue.take(); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(consumer).start(); new Thread(consumer).start(); }
使用Condition實現生產者消費者模式
以下代碼其實也是相似ArrayBlockingQueue內部的實現原理.
以下代碼所示,定義了一個隊列容量是16的的queue,用來存放數據,定義一個ReentrantLock類型的鎖,並在Lock鎖的基礎上建立了兩個Condition,一個是notEmpty一個是notFull,分別表明隊列沒有空和沒有滿的條件,而後就是put和take方法.
put方法中,由於是多線程訪問環境,因此先上鎖,而後在while條件中判斷queue中是否已經滿了,若是滿了,則調用notFull的await()方法阻塞生產者並釋放Lock鎖,若是沒有滿則往隊列中放入數據,而且調用notEmpty.singleAll()方法喚醒全部的消費者線程,最後在finally中釋放鎖.
同理take方法和put方法相似,一樣是先上鎖,在判斷while條件是否知足,而後執行對應的操做,最後在finally中釋放鎖.
public class MyBlockingQueue2 { private Queue queue; private int max; private ReentrantLock lock=new ReentrantLock(); private Condition notEmpty = lock.newCondition(); private Condition notFull =lock.newCondition(); public MyBlockingQueue2(int size){ this.max =size; queue = new LinkedList(); } public void put(Object o) throws InterruptedException { lock.lock(); try { while (queue.size() == max) { notFull.await(); } queue.add(o); //喚醒全部的消費者 notEmpty.signalAll(); } finally { lock.unlock(); } } public Object take() throws InterruptedException{ lock.lock(); try { //這裏不能改用if判斷,由於生產者喚醒了全部的消費者, //消費者喚醒後,必須在進行一次條件判斷 while (queue.size() == 0) { notEmpty.await(); } Object remove = queue.remove(); //喚醒全部的生產者 notFull.signalAll(); return remove; }finally { lock.unlock(); } } }
使用wait/notify實現生產者消費者模式
以下代碼所示,利用wait/notify實現生產者消費者模式主要是在put和take方法上加了synchronized鎖,而且在各自的while方法中進行條件判斷
public class MyBlockingQueue3 { private int max; private Queue<Object> queue; public MyBlockingQueue3(int size){ this.max =size; this.queue=new LinkedList<>(); } public synchronized void put(Object o) throws InterruptedException { while(queue.size() == max){ wait(); } queue.add(o); notifyAll(); } public synchronized Object take() throws InterruptedException { while (queue.size() == 0){ wait(); } Object remove = queue.remove(); notifyAll(); return remove; } }
以上就是三種實現生產者消費者模式的方式,第一種比較簡單直接利用ArrayBlockingQueue內部的特徵完成生產者消費者模式的實現場景,第二種是第一種背後的實現原理,第三種利用synchronzied實現.