1. 程式人生 > >java同步機制

java同步機制

java同步機制的幾種方式

出現執行緒安全問題:

如果存在多個執行緒對共享資源競爭,就可能發生執行緒安全問題。
一般解決執行緒安全問題,需要➕鎖

synchronized同步方法

對於非static方法加上synchronized,是對當前物件加鎖。而如果對static方法加上suychronized關鍵字,是對當前類物件-class物件加鎖。
1. 如果一個執行緒訪問一個物件的synchronized方法,那麼其它執行緒不能訪問該物件的synchronized方法。
2. 但是其它執行緒可以訪問這個物件的非synchronized方法。
3. 當然,對於一個類的不同例項之間互不影響。
4. 如果一個執行緒執行一個物件的非static synchronized方法,另外一個執行緒執行這個物件所屬類的static synchronized方法,不會互斥。因為一個是物件鎖,一個是類鎖。
5. 對於synchronized同步程式碼塊或者同步方法,當出現異常的時候,虛擬機器會自動釋放當前執行緒佔用的鎖,因此不會出現由於異常導致出現死鎖現象。

synchronized原理

對於同步程式碼塊用javap -c com.demo.synchronzeddemo反編譯成位元組碼的時候,會在同步塊的入口和出口分看到monitorenter,monitorexit ,這兩個指令。
其實對於每個物件都有一個監視器鎖,monitor,當monitor被佔用的時候就會處於鎖定狀態,執行緒執行monitorenter的時候會嘗試獲取monitor的所有權。
1. 如果monitor的進入數為0,則該執行緒進入monitor,然後將進入數設定為1,該執行緒為monitor的所有者;
2. 如果執行緒為該monitor的所有者,重新進入,則進入monitor的進入數+1;
3. 如果其它執行緒佔用了monitor,則該執行緒進入阻塞狀態,直到monitor的進入數為0,則重新嘗試獲取monitor的所有權。(非公平鎖)
執行monitorexit的執行緒必須是objectref所對應的monitor的持有者。
1. 指令執行的時候,monitor的進入數-1,如果-1後為0,那麼執行緒推出monitor,不再是這個monitor的所有者其它被阻塞的執行緒嘗試獲取monitor。
類似的,wait,notify方法也是依賴於monitor物件,所以只有在同步方法或者同步程式碼塊中才能呼叫wait,notify,否則會丟擲java.lang.illegalmonitorstateexception.
如果是同步方法,編譯成普通方法的呼叫和返回指令,在jvm位元組碼層面沒有任何特別的指令來實現同步方法,而是在class檔案的方法表中將該方法的access_flags欄位中的synchronized標誌為1。如果設定了,那麼執行執行緒將先獲取monitor,成功以後才執行方法體。
synchronised實現了自旋鎖,線上程進入ContentionList時,即第一步操作之前,執行緒在等待進入等待佇列的時候,首先進行自旋嘗試獲得鎖,如果不成功,進入等待佇列。另外,自旋執行緒可能會搶佔了ready執行緒的鎖,自旋鎖由每一個監視物件維護,每個監視物件一個。
在這裡插入圖片描述

  1. 首先使用者自旋嘗試獲得鎖,如果失敗,加入ContentionList虛擬佇列中。虛擬佇列新加入在佇列頭部,每次從佇列尾部選取。因為虛擬佇列的尾部會發生執行緒併發訪問,為了降低對於隊尾的爭用,建立了EntryList。Owner執行緒會在unlock的時候從contentionlist中遷移到entry list中,並指定entrylist中的某個執行緒為ready。owner執行緒並不是直接吧鎖傳遞給ready,只是把競爭鎖的一個權利交給它,此時還需要和新進來的執行緒(自旋)競爭。
  2. 而owner執行緒被wait方法阻塞,則轉移到waitset佇列中,如果某個時刻被notify喚醒,則再次轉移到entrylist中。

volatile關鍵字解析

java記憶體模型的相關概念

java虛擬機器定義了一種java記憶體模型來遮蔽硬體平臺和作業系統的記憶體訪問差異,以實現讓java程式在各個平臺都能達到一致的記憶體訪問效果。
主要是定義了程式的變數訪問規則,也就是定義了程式執行的次序。
java記憶體模型規定所有的變數都存在於主存,每個執行緒都有自己的工作記憶體。執行緒對於變數的所有操作都必須在自己的工作記憶體內,不能直接對主存操作。每個執行緒都不能訪問別的執行緒的工作記憶體。

併發程式設計的三個概念:原子性,可見性,有序性

原子性:java記憶體模型保證了基本讀取和賦值是原子操作。

  1. 可見性:volatile關鍵字保證了可見性
  2. 有序性:指令重排序不會影響單執行緒程式的執行,但是會影響多執行緒安全。

深入剖析volatile關鍵字:

volatile關鍵字的兩層語義:

  1. 保證了不同執行緒對於這個變數進行操作時的可見性;
  2. 禁止指令重排序。
    也就是說volatile保證了可見性和有序性。如果被volatile修飾的共享變數,一旦發生修改,會強制將修改的值立即寫入主存,同時別的執行緒的工作記憶體中的該變數快取失效,需要重新去主存讀取。也就是說volatile保證了可見性和有序性。如果被volatile修飾的共享變數,一旦發生修改,會強制將修改的值立即寫入主存,同時別的執行緒的工作記憶體中的該變數快取失效,需要重新去主存讀取。
    同時,volatile保證了有序性,被volatile修飾的變數相對位置不可改變。
  3. volatile不保證原子性
    如果要保證原子性,需要synchronized或者lock鎖,或者用atomic包下的一些原子操作。(利用cas)保證原子操作。
    原因:i++,load,increment,store,storeload barrier,四步,記憶體屏障保證最後一步把寫入的值重新整理到快取,但是,從load到store不是安全的,如果期間其它執行緒修改值,那麼值會丟失。
  4. volatile的原理和實現機制
    加入volatile關鍵字以後,會多出一個lock字首指令,彙編程式碼。實際上相當於一個記憶體屏障,cpu指令。
    這個記憶體屏障,確保重排序的時候記憶體屏障之前的程式碼不會跑到屏障後面;同時強制將快取的修改操作立即寫入主存;如果是寫操作,導致其它工作記憶體中對應的快取行無效。

volatile的應用場景:

我的理解是,必須是滿足原子性
1. 狀態標記量;
2. 雙重檢驗。程式碼示例:

class Singleton{
		private volatile static Singleton instance = null;
		private Singleton{
			
		}
		public static Singleton getInstance(){
			if(instance == null){
				synchronized(Singleton.class){
					if(instance == null){
						instance = new Singleton();}}}
		return instance;}
	}

lock鎖

synchronized的缺陷

  1. 對於synchronized鎖來說,只有執行完畢或者異常才會釋放鎖,否則一直堵塞,且沒辦法中斷等待;
  2. 同時對於synchronized鎖來說,無論是讀還寫,同一時段都只有一個執行緒可以做到,對於很多讀執行緒,效率低下。

java.util.concurrent.locks包下的lock

Lock是一個介面,裡面定義了5個方法,主要是獲得鎖和釋放鎖。
其中lock()的用法如右圖所示。
tryLock()會返回,無論是否拿到鎖都會立刻返回。
tryLock(long time, timeout unit)會等待一段時間。
lockInterruptibly(),當通過這個方法去獲得鎖的時候,如果執行緒正在等待鎖,可以響應中斷。

ReentrantLock

可重入鎖,唯一一個實現了Lock介面的類。

ReadWriteLock

也是一個介面。主要:一個readLock(),一個readLock()。
讀鎖不互斥,讀寫互斥。

公平鎖

ReentrantLock預設是非公平鎖,但是可以在新建的時候戲而已引數(true)