在併發編程中,保證線程同步,從而實現線程之間正確通訊,是一個值得考慮的問題。本篇將參考許多著名書籍,學習如何讓多個線程之間相互配合,完成咱們指定的任務。面試
固然本文只是學習了一部分線程間通訊的方法,還有一些例如使用Lock和Condition對象,管道輸入輸出、生產者消費者等內容,咱們以後再作學習。編程
synchronized
關鍵字是Java提供的互斥的內置鎖,該鎖機制不用顯式加鎖或者釋放鎖。互斥執行的特性能夠確保對整個臨界區代碼的執行具備原子性,同步機制保證了共享數據在同一個時刻只被一個線程使用。併發
回顧如下synchronized的底層實現:學習
咱們能夠對下面這段代碼進行反編譯:javap -v TestData.class
。this
public class TestData { public static synchronized void m1(){} public synchronized void m2(){} public static void main(String[] args) { synchronized (TestData.class){ } } }
編譯結果以下:
線程
雖然同步方法和代碼塊的實現細節不一樣,可是歸根結底:JVM對於方法或者代碼塊的實現是基於對Monitor對象的進入和退出操做。code
以同步代碼塊舉例:對象
下圖源自《Java併發編程得藝術》4-2
blog
新Java內存模型中提供了比鎖更加輕量級的通訊機制,它加強了volatile的內存語義,讓volatile擁有和鎖同樣的語義:告知程序任何對volatile修飾變量的訪問都要從共享內存中獲取,對它的改變必須同步刷新回共享內存,保證了線程對變量訪問的可見性。
關於volatile的重點學習,以後再作總結。
等待/通知相關的方法被定義在java.lang.Object
上,這些方法必須由鎖對象來調用。同步實例方法爲this,靜態方法爲類對象,代碼塊的鎖是括號裏的玩意兒。
這些方法必須須要獲取鎖對象以後才能調用,也就是必需要在同步塊中或同步方法中調用,不然會拋出IllegalMonitorStateException
的異常。
wait()
: 調用該方法的線程進入WAITING
狀態,並釋放對象的鎖,此時當前線程只有被其餘線程通知或中斷纔會返回。
wait(long)
、wait(long, int)
:進入TIMED_WAITING
狀態,釋放鎖,當前線程有通知或中斷會返回,時間到了也會返回。
notify()
: 當前線程通知一個在該對象上等待的另外一線程,被喚醒的線程從等待隊列(WAITING)被移動到同步隊列(BLOCKED)中,意思是被喚醒的線程不會當即執行,須要等當前線程釋放鎖以後,而且在同步隊列中的線程獲得了鎖才能執行。
notifyAll()
:當前線程通知全部等待在該對象上的線程,將全部在等待隊列中的線程所有移到同步隊列中。
假設A和B須要獲取同一把鎖,A進入以後,B進入同步隊列,陷入阻塞(BLOCKED)。
若是A中調用鎖的wait()方法,A釋放鎖,並陷入等待(WAITING)。此時另一個線程B獲取的當前鎖,B運行。
若是此時B中調用鎖的notify()方法,A被喚醒,從等待隊列轉移到同步隊列,只有B運行完畢了,鎖被釋放了,A拿到鎖了,A纔出來運行。
等待/通知機制依託於同步機制,確保等待線程從wait()方法返回時可以感知到通知線程對變量作出的修改。
sleep()和wait()方法均可以讓線程放棄CPU一段時間,進入等待(WAITING)狀態。
sleep()靜態方法定義在Thread類中,wait()定義在Object類中。
若是線程持有某個對象的監視器,wait()調用以後,當前線程會釋放鎖,而sleep()則不會釋放這個鎖。
對於放棄對象監視器,wait()方法和notify()/notifyAll()有必定區別:
鎖對象調用wait()方法以後,會當即釋放對象監視器。而notify()/notifyAll()則不會當即釋放,而是等到線程剩餘代碼執行完畢以後纔會釋放監視器。
參考書籍:《Java併發編程的藝術》 方騰飛