同步的基本思想
為了保證共享數據在同一時刻只被一個線程使用,我們有一種很簡單的實現思想,就是
在共享數據里保存一個鎖 ,當沒有線程訪問時,鎖是空的。
當有第一個線程訪問時,就 在鎖里保存這個線程的標識 並允許這個線程訪問共享數據。
在當前線程釋放共享數據之前,如果再有其他線程想要訪問共享數據,就要 等待鎖釋放 。
- 在共享數據里保存一個鎖
- 在鎖里保存這個線程的標識
- 其他線程訪問已加鎖共享數據要等待鎖釋放
Jvm同步的實現
jvm中有以下三種鎖(由上到下越來越“重量級”):
- 偏向鎖
- 輕量級鎖
- 重量級鎖
重量級鎖
Synchronized 原理
我們直接參考JVM規范中描述:每個對象有一個監視器鎖(monitor)。
當monitor被占用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:
1、如果monitor的進入數為0,則該線程進入monitor,然后將進入數設置為1,該線程即為monitor的所有者。
2、如果線程已經占有該monitor,只是重新進入,則進入monitor的進入數加1.
3.如果其他線程已經占用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor的所有權。
Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,
這就是為什么只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。
Synchronized是通過對象內部的一個叫做監視器鎖(monitor)來實現的。
但是監視器鎖本質又是依賴於底層的操作系統的互斥鎖(Mutex Lock)來實現的。而操作系統實現線程之間的切換這就需要從用戶態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是為什么Synchronized效率低的原因。
因此,這種依賴於操作系統互斥鎖(Mutex Lock)所實現的鎖我們稱之為“重量級鎖”。
輕量級鎖
鎖的狀態總共有四種:無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。
JDK 1.6中默認是開啟偏向鎖和輕量級鎖的,我們也可以通過-XX:-UseBiasedLocking來禁用偏向鎖。
輕量級鎖的核心思想就是“被加鎖的代碼不會發生並發,如果發生並發,那就膨脹成重量級鎖(膨脹指的鎖的重量級上升,一旦升級,就不會降級了)”。
輕量級鎖依賴了一種叫做CAS(compare and swap)的操作。
術語定義
術語 | 英文 | 說明 |
CAS | Compare and Swap | 比較並設置。 用於在硬件層面上提供原子性操作。 在 Intel 處理器中,比較並交換通過指令cmpxchg實現。比較是否和給定的數值一致,如果一致則修改,不一致則不修改。 |
偏向鎖
根據輕量級鎖的實現,我們知道雖然輕量級鎖不支持“並發”,遇到“並發”就要膨脹為重量級鎖,但是輕量級鎖可以支持多個線程以串行的方式訪問同一個加鎖對象。
比如A線程可以先獲取對象o的輕量鎖,然后A釋放了輕量鎖,這個時候B線程來獲取o的輕量鎖,是可以成功獲取得,以這種方式可以一直串行下去。
之所以能實現這種串行,是因為有一個釋放鎖的動作。那么假設有一個加鎖的java方法,這個方法在運行的時候其實從始至終只有一個線程在調用,但是每次調用完卻也要釋放鎖,下次調用還要重新獲得鎖。
那么我們能不能做一個假設:“假設加鎖的代碼從始至終就只有一個線程在調用,如果發現有多於一個線程調用,再膨脹成輕量級鎖也不遲”。這個假設,就是偏向鎖的核心思想。
偏向鎖依賴了一種叫做CAS(compare and swap)的操作。
總結
本文重點介紹了JDk中采用輕量級鎖和偏向鎖等對Synchronized的優化,
但是這兩種鎖也不是完全沒缺點的,比如競爭比較激烈的時候,不但無法提升效率,反而會降低效率,因為多了一個鎖升級的過程,這個時候就需要通過-XX:-UseBiasedLocking來禁用偏向鎖。下面是這幾種鎖的對比:
鎖 |
優點 |
缺點 |
適用場景 |
偏向鎖 |
加鎖和解鎖不需要額外的消耗,和執行非同步方法比僅存在納秒級的差距。 |
如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。 |
適用於只有一個線程訪問同步塊場景。 |
輕量級鎖 |
競爭的線程不會阻塞,提高了程序的響應速度。 |
如果始終得不到鎖競爭的線程使用自旋會消耗CPU。 |
追求響應時間。 同步塊執行速度非常快。 |
重量級鎖 |
線程競爭不使用自旋,不會消耗CPU。 |
線程阻塞,響應時間緩慢。 |
追求吞吐量。 同步塊執行速度較長。 |