Java併發同步機制
一、Synchronized
1》實現方式及原理
它的使用方法有以下三種
- 同步普通方法,鎖當前實例對象(對這個類的對象操作需要獲取鎖)
- 同步靜態方法,鎖當前Class對象(這個類new的對象不會加鎖,而調用類的靜態方法需要獲取鎖)
- 同步代碼塊,鎖代碼塊範圍內的對象
一個線程訪問一個類的static synchronized方法時,其他線程可以訪問該類的非static synchronized的方法。原因是兩個線程所獲得的鎖不同,前者是鎖住的是這個類,而後者鎖着的是這個實例對象,因此不存在互斥關係。
在JVM層面,synchronized的實現是通過monitor指令實現的
同步代碼塊:
- 進入同步範圍時,monitorentry指令
- 退出代碼塊範圍或者異常退出時,monitorexit指令
同步方法:
- 執行到synchronized修飾的方法時會有ACC_SYNCHRONIZED標誌
2》鎖升級
jdk1.6對synchronized進行了大量優化
- 偏向鎖
- 輕量級鎖
- 重量級鎖
偏向鎖:大多數情況下認爲線程是不存在競爭的,這樣加鎖和釋放鎖無疑是浪費了資源,一個線程訪問資源時,如果對象頭中存儲的線程ID爲這個線程的ID時,不需要進行CAS加鎖和釋放鎖。(偏向鎖升級成輕量級鎖)線程ID不爲此線程時,檢查對象頭中鎖標誌是否爲偏向鎖,如果不是,則通過CAS競爭鎖,如果是,則通過CAS將對象頭的線程ID指向當前線程。此時如果CAS修改對象頭的線程ID失敗,則證明出現鎖的競爭,鎖升級成輕量級鎖。
輕量級鎖:加鎖之前,JVM會在當前棧幀中創建存儲鎖記錄的空間,將對象頭中的Mark Word 複製到鎖記錄中,然後線程使用CAS將對象頭中的Mark Word的線程ID改成指向當前線程,如果成功,獲取鎖。(輕量級鎖升級成重量級鎖)如果CAS失敗,則進入自旋狀態嘗試獲取該鎖,如果直到自旋結束都沒有獲取成功,則該輕量級鎖膨脹爲重量級鎖,並且阻塞後面的其他競爭該鎖的線程。當獲取鎖的線程執行完畢,此時釋放鎖通過CAS將對象頭中的信息重新替換回去,如果CAS成功則線程成功釋放鎖,如果CAS失敗則說明存在其他線程競爭此時鎖已經膨脹爲重量級鎖,此時釋放鎖並且喚醒被阻塞的線程。
重量級鎖:多個線程進行競爭鎖,只有一個會獲取鎖,其餘進入阻塞隊列。其實現的原理都離不開monitor(監視器),monitor包含一下屬性,owner:初始爲null,線程佔有鎖時,owner指向該線程,entryset:競爭鎖的隊列,waitset:當對象調用wait()時,線程進入此隊列,當對象調用notify或者notifyall時,重新進入entryset。關係如下圖
3》鎖優化
- 自旋鎖
- 鎖粗化
- 鎖消除
自旋鎖就是輕量級鎖那樣,不進行阻塞,先自旋獲取鎖。鎖粗化是對於同一個對象一系列的加鎖,例如循環中使用StringBuffer,每次append都會加鎖,jvm調優會執行鎖粗化,就會每次讓加鎖範圍到第一個append開始,到第二個append結束,每兩次加一個鎖,這樣無疑提高了效率。鎖消除就是對於局部變量(不可能共享的數據)進行加鎖,jvm會進行無鎖化。
二、volatile
volite關鍵字有兩個特性:可見性和有序性。
可見性需要了解Java內存模型(JMM),這裏簡單介紹一下,JMM的提出是爲了解決不同操作系統的內存讀取存放的規則不同而造成的困難,實現內存讀寫的同一,每個線程有自己的工作內存,也叫做緩存,線程更改數據會先存到緩存中,然後在更新到主內存中,主內存是公共內存,是線程間進行交流的方式。
1》可見性
當被volite關鍵字修飾的變量值改變了,lock前綴指令會使其直接更新到主內存中,緩存一致性原則會使其他線程的緩存失效,只能從主內存中重新獲取。
2》有序性
當執行到volite關鍵字時,變量會更新到主存,保證可見性,此時,如果其他線程以此變量的參數爲啓動條件,而volite之後的代碼還沒執行,volite保證了這種情況不會發生,即禁止指令重排。