前言
java提供兩種鎖:lock
和synchronized
,關于二者的區別,網上一個說法非常好:
synchronized
相當于自動擋汽車,使用簡單,可以覆蓋大部分使用場景
但如果你想玩漂移等特殊操作,就需要lock
,使用相對麻煩,但可以實現一些特殊場景,如公平鎖
實際上二者出身就有本質區別,synchronized
是官方的,而lock
是民間的李二狗寫出來的,起初剛開始lock
無論在功能上還是性能上都超越了synchronized
,可以說狠狠的打了官方的臉。
因此在1.5版本之后官方通過鎖升級對synchronized
的性能做了提升,本文主要簡單介紹synchronized
鎖升級的過程
synchronized
synchronized
是一種對象鎖(鎖的是對象而非引用),作用粒度是對象,java中每個對象都可以上鎖(同一時間只有一個線程能上鎖成功),而且通過對象內部存儲的markword
標記鎖狀態。
synchronized
加鎖方式
1、同步實例方法,鎖是當前實例對象
2、同步類方法,鎖是當前類對象
3、同步代碼塊,鎖是括號里面的對象
public class Syc {
Object lock = new Object();
public synchronized static void go() { // 鎖的是Syc.class對象
}
public synchronized void say() { // 鎖的是Syc對象實例
synchronized (lock) { // 鎖的是lock對象實例
}
}
}
鎖升級
首先過一下synchronized
鎖升級的過程
1.偏向鎖
當只有一個線程獲得了鎖,鎖就進入偏向模式,MarkWord
標識偏向狀態,當這個線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操作。
2.輕量級鎖
當有其它線程要獲取鎖,競爭不是很激烈,鎖進入輕量級鎖,MarkWord
標識輕量級狀態,此時等待鎖的線程開始自旋,即空循環等待鎖釋放,此過程不釋放cpu。
3.重量級鎖
當獲取鎖的競爭變的激烈,比如來了很多個線程或者某個線程自旋等待的次數太多了,鎖進入重量級鎖,MarkWord
標識重量級狀態,重量級鎖依賴操作系統的Mutex lock
實現,此時等待鎖的線程掛起,當鎖釋放后再由操作系統喚醒重新嘗試獲取鎖,由于借助操作系統,導致用戶態內核態切換,此過程時間成本比較高。
原始的synchronized
是直接使用重量級鎖,才會導致性能很低,加入鎖升級才使得synchronized
性能獲得很大提升。
理解
以上講解了synchronized
鎖升級的過程,如果不好理解,還是拿現實生活舉個例子:
假設某公司有多個
會議室
,每個團隊
需要獲取到會議室的鎖
才能進去開會,會議室門口掛著一個寫字板
,時刻記錄當前會議室使用狀態。
-
會議室
相當于對象 -
團隊
相當于線程 -
會議室的鎖
相當于對象的鎖 -
寫字板
相當于MarkWord
1.偏向鎖
公司發現大部分時間,同一個會議室都是同一個團隊占用,于是當A團隊第一次占用會議室時,在寫字板上寫上
偏向 A團隊
,下次A團隊進入不用修改就可以直接進入會議室,大大提升了開會效率。
如果B團隊想使用會議室,此時A團隊已經不使用該會議室,則修改寫字板偏向 B團隊
。
這就是偏向鎖,經過研究發現,在大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,因此為了減少同一線程獲取鎖帶來的開銷,所以引入偏向鎖
。
2.輕量級鎖
如果B團隊想使用會議室,此時A還占用著會議室(寫字板上記錄
偏向 A團隊
),此時出現了競爭,寫字板上修改為輕量競爭
,B團隊哪也不去,就在會議室外原地打轉(自旋)等著,因為公司大部分會議時間都很短,B相信A一般會很快出來。
如果A確實一會就出來了,B馬上去搶會議室的鎖。
這就是輕量級鎖,偏向鎖出現了競爭會升級為輕量級鎖
,因為大部分線程占用鎖的時間不會特別長,所以等待線程剛開始不需要掛起,只需要通過空轉自旋等待,一般很快就會獲取到鎖,比過程一直占用著cpu。
3.重量級鎖
上面的情況,如果B等了很久A都不出來,或者這段時間公司特別繁忙,各團隊頻繁開會,還有C,D,E...等等團隊也要使用該會議室,這時如果A在里面開會沒完沒了,其它團隊一直在外面傻轉著也不是事。
這時候就要請會議室管理員
幫忙了,他讓各團隊都回去睡覺吧,寫字板上修改為重量競爭
,等A團隊開完會出來,我負責通知其它團隊,你們再過來搶會議室的鎖。
這樣在會議室競爭特別激烈時,請會議室管理員
幫忙有效的避免了等待團隊傻等,但如果在競爭不激烈的情況下就沒有必要請出會議室管理員
,畢竟造成額外開銷,而且靠會議室管理員
通知再來搶會議室肯定比站會議室外面等要慢很多。
這就是重量級鎖,其中會議室管理員
相當于操作系統,當某個線程自旋次數過多或者多個線程同時競爭鎖,鎖競爭變的激烈,輕量級鎖升級為重量級鎖
,此時等待線程都掛起,對象鎖釋放后再由操作系統喚醒線程,此過程開銷很大。
synchronized
最開始就是不管競爭激不激烈都使用重量級鎖導致性能很低,但競爭激勵時如果任由等待線程空轉消耗跟大,所以競爭激勵升級為重量級鎖也是非常合理。
其它
再補充幾個問題
可重入鎖
synchronized
是一種可重入鎖,比如有兩個方法A,B鎖的都是同一個對象,其中A調用B,那么某線程獲取鎖后進入A方法也能順利進入B方法,即自己不會鎖自己(否則synchronized
修飾的方法都不能遞歸了),常用的ReentrantLock
也是可重入鎖。
公平/非公平
從上面的描述也會發現,當某個線程釋放鎖,其它線程會重新競爭鎖,沒有先來后到,就跟搶公交一樣一擁而上,這就是不公平
,我們的 synchronized就是一個非公平鎖。
如果想實現公平鎖,可以用ReentrantLock
,他會維護一個隊列,先到先得,就像排隊上地鐵,文明多了。
over~