Synchronize 是什麼
synchronized,中文意思為同步,用於多線程資源共享與維護的最常用手段。它通過線程互斥的手段,保存證了資源的原子性。
使用如下:
<code>synchronized
(o
) { } /<code>
實現的原理
本文要講的主要是1.6以後的版本,1.6版本針對synchronize做了使用優化,根據使用的情使用不同的鎖。
包括:
偏向鎖:
1:當線程取得鎖時, warkword偏向鎖位標記為1,並記錄使用該對象的線程指針。
2:當線程嘗試向 偏向鎖對象 取鎖時, 鎖將升級為輕量級鎖。
輕量級鎖:
1.當線程拿不到鎖對象時,線程不會釋放,而是繼續while循環空轉,直到獲取到鎖為止。
2.輕量級鎖的Markword會記錄lock Record的指針,lock Record會記錄對象自旋的次數,當它達到一定自旋次數之後,jvm會將它升級為重量級鎖。
<code>優點: 無需從用戶態轉向內核態,在鎖競爭比較低的情況,線程只需消化幾個時鐘週期就能獲得鎖,所以性能很快。 缺點: 線程自旋是需要消耗cpu性能的,在鎖競爭激烈的情況,空轉的線程數量和自旋的次數會變高,此時會白白浪費cpu時鐘週期。/<code>
重量級鎖:
jvm對重量級鎖的實現,是需要依賴操作系統底層的,操作系統底層維護了一個鎖的隊列,當jvm所有重量級鎖的申請,都需要在這個鎖隊列裡面進行排隊,線程需要從用戶態轉向內核態,排隊過程線程被掛起,無需消耗cpu時鐘頻率,直到輪到這個線程獲取鎖時,系統才會喚醒該爭用的線程。
<code>優點: 不消耗cpu,特別時對於大量鎖的爭用時。 缺點: 等待鎖的時間長。/<code>
synchronize 鎖升級過程
如下圖所示:
- 初始化,無鎖。
- 有且只有一線程取得鎖時,為偏向鎖。
- 對象已被鎖,並有其它線程嘗試取鎖時,鎖升級為輕量級鎖。
- 鎖狀態為輕量級鎖,並有更多(達到臨界值時)的線程嘗試去取鎖,輕量級鎖將升級為重量級鎖。
線程獲取鎖過程
如下圖所示:
- 對象無鎖時。直接取得鎖
- 有鎖並且是輕量級鎖,線程自旋取得鎖。
- 有鎖並且是重量級鎖,線程阻塞,等待鎖釋放再取得鎖。
- 取得鎖後,用完釋放。
鎖狀態與markword
對象鎖的狀態是存在markword記錄的,如下圖所示:
無鎖時,鎖標誌為01,另外存儲了其它各種信息(包括偏向鎖狀態,分代年齡,hashcode)。
偏向鎖時,鎖標誌為01,另外存儲了取得鎖的線程。
輕量鎖時,鎖標誌為00,存儲了線程棧Lock Record的指針。
重量級鎖時,鎖標誌為10,存儲了,重量級鎖的指針。
實例
下面將通過一個實例,結合markword一步一步地演示synchronize 鎖的升級過程。
測試過程
- 引入JOL打印出對象的結構(關鍵是markword)。
- 模擬無鎖,單線程鎖,2個線程,100個級線。
- 輸出對應的markword。
pom.xml 配置
<code><
project
xmlns
="http://maven.apache.org/POM/4.0.0"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
><
modelVersion
>4.0.0modelVersion
><
groupId
>SynchronizeTestgroupId
><
artifactId
>SynchronizeTestartifactId
><
version
>1.0-SNAPSHOTversion
><
dependencies
><
dependency
><
groupId
>org.openjdk.jolgroupId
><
artifactId
>jol-coreartifactId
><
version
>0.9version
>dependency
>dependencies
>project
>/<code>
代碼:
<code>import
org.openjdk.jol.info.ClassLayout;public
class
ApplicationTest
{static
volatile
String strMsg =""
;static
volatile
String str2Msg =""
;public
static
void
main
(String[] args)
throws
InterruptedException { Thread.sleep(5000
); Object o =new
Object();new
Thread() {public
void
run
()
{while
(true
) {if
(str2Msg.equals(strMsg) ==false
) { str2Msg = strMsg; System.out.print(str2Msg); } } } }.start(); strMsg = ClassLayout.parseInstance(o).toPrintable(); }public
static
void
SynsTest
(
int
num,final
Object o)throws
InterruptedException {for
(int
i =0
; i < num; i++) { System.out.println("啟開第"
+ (i +1
) +"線程"
);new
Thread() {public
void
run
()
{synchronized
(o) {for
(; ; ) { strMsg = ClassLayout.parseInstance(o).toPrintable();try
{ Thread.sleep(100
); } }catch
(InterruptedException e) { e.printStackTrace(); } } } }.start(); } } }/<code>
測試步驟:
第一步:
1、o對象初始化後,直接打印出o對象。解注以下代碼:
<code> strMsg = ClassLayout.parseInstance(o).toPrintable(); /<code>
2、運行結果如下圖所示: 狀態位為01,斷定為無鎖。
第二步:
1、開啟1個線程取鎖。解注以下代碼:
<code>SynsTest
(1
, o); /<code>
2、結果如下圖所示:狀態位為01,後面還記錄著 對應的線程指針,斷定為 偏向鎖。
第三步:
1、開啟2個線程最鎖。解注以下代碼:
<code>SynsTest
(2
,o); /<code>
2、如下圖所示:狀態位為00,斷定為輕量級鎖。
第四步:
1、開始100個線程取鎖。解注以下代碼:
<code>SynsTest
(100
,o);/<code>
2、結果如下圖所示:狀態位為10,斷定為重量級鎖。
PS:
因為偏向鎖默認在jvm啟動4秒後才啟動。所以在這裡設置等待5秒。具體的結果與配置有關。本機測試用的環境如下,所有配置都是默認的。
可以通過以下命令打印出配置的參數:
小編整理來自:https://blog.csdn.net/richyliu44/article/details/105327885,CSDN博主「Richy Liu」
今天小編整理的這篇乾貨您覺得怎麼樣呢?是否對自己有一定的提升呢,如果您覺得還可以的話,不妨點個關注收藏轉發一下吶!與小編一起進步!您的支持就是小編最大的動力!謝謝啦!關注我!每天更新哦!絕對技術乾貨!