根據分類標准我們把鎖分為以下 7 大類別,分別是:
- 偏向鎖/輕量級鎖/重量級鎖;
- 可重入鎖/非可重入鎖;
- 共享鎖/獨占鎖;
- 公平鎖/非公平鎖;
- 悲觀鎖/樂觀鎖;
- 自旋鎖/非自旋鎖;
- 可中斷鎖/不可中斷鎖。
以上是常見的分類標准,下面我們來逐一介紹它們的含義。
偏向鎖/輕量級鎖/重量級鎖
第一種分類是偏向鎖/輕量級鎖/重量級鎖,這三種鎖特指 synchronized 鎖的狀態,通過在對象頭中的 mark word 來表明鎖的狀態。
偏向鎖
如果自始至終,對於這把鎖都不存在競爭,那么其實就沒必要上鎖,只需要打個標記就行了,這就是偏向鎖的思想。一個對象被初始化后,還沒有任何線程來獲取它的鎖時,那么它就是可偏向的,當有第一個線程來訪問它並嘗試獲取鎖的時候,它就將這個線程記錄下來,以后如果嘗試獲取鎖的線程正是偏向鎖的擁有者,就可以直接獲得鎖,開銷很小,性能最好。
輕量級鎖
JVM 開發者發現在很多情況下,synchronized 中的代碼是被多個線程交替執行的,而不是同時執行的,也就是說並不存在實際的競爭,或者是只有短時間的鎖競爭,用 CAS 就可以解決,這種情況下,用完全互斥的重量級鎖是沒必要的。輕量級鎖是指當鎖原來是偏向鎖的時候,被另一個線程訪問,說明存在競爭,那么偏向鎖就會升級為輕量級鎖,線程會通過自旋的形式嘗試獲取鎖,而不會陷入阻塞。
重量級鎖
重量級鎖是互斥鎖,它是利用操作系統的同步機制實現的,所以開銷相對比較大。當多個線程直接有實際競爭,且鎖競爭時間長的時候,輕量級鎖不能滿足需求,鎖就會膨脹為重量級鎖。重量級鎖會讓其他申請卻拿不到鎖的線程進入阻塞狀態。
你可以發現鎖升級的路徑:無鎖→偏向鎖→輕量級鎖→重量級鎖。
綜上所述,偏向鎖性能最好,可以避免執行 CAS 操作。而輕量級鎖利用自旋和 CAS 避免了重量級鎖帶來的線程阻塞和喚醒,性能中等。重量級鎖則會把獲取不到鎖的線程阻塞,性能最差。
可重入鎖/非可重入鎖
第 2 個分類是可重入鎖和非可重入鎖。可重入鎖指的是線程當前已經持有這把鎖了,能在不釋放這把鎖的情況下,再次獲取這把鎖。同理,不可重入鎖指的是雖然線程當前持有了這把鎖,但是如果想再次獲取這把鎖,也必須要先釋放鎖后才能再次嘗試獲取。
對於可重入鎖而言,最典型的就是 ReentrantLock 了,正如它的名字一樣,reentrant 的意思就是可重入,它也是 Lock 接口最主要的一個實現類。
共享鎖/獨占鎖
第 3 種分類標准是共享鎖和獨占鎖。共享鎖指的是我們同一把鎖可以被多個線程同時獲得,而獨占鎖指的就是,這把鎖只能同時被一個線程獲得。我們的讀寫鎖,就最好地詮釋了共享鎖和獨占鎖的理念。讀寫鎖中的讀鎖,是共享鎖,而寫鎖是獨占鎖。讀鎖可以被同時讀,可以同時被多個線程持有,而寫鎖最多只能同時被一個線程持有。
公平鎖/非公平鎖
第 4 種分類是公平鎖和非公平鎖。公平鎖的公平的含義在於如果線程現在拿不到這把鎖,那么線程就都會進入等待,開始排隊,在等待隊列里等待時間長的線程會優先拿到這把鎖,有先來先得的意思。而非公平鎖就不那么“完美”了,它會在一定情況下,忽略掉已經在排隊的線程,發生插隊現象。
悲觀鎖/樂觀鎖
第 5 種分類是悲觀鎖,以及與它對應的樂觀鎖。悲觀鎖的概念是在獲取資源之前,必須先拿到鎖,以便達到“獨占”的狀態,當前線程在操作資源的時候,其他線程由於不能拿到鎖,所以其他線程不能來影響我。而樂觀鎖恰恰相反,它並不要求在獲取資源前拿到鎖,也不會鎖住資源;相反,樂觀鎖利用 CAS 理念,在不獨占資源的情況下,完成了對資源的修改。
自旋鎖/非自旋鎖
第 6 種分類是自旋鎖與非自旋鎖。自旋鎖的理念是如果線程現在拿不到鎖,並不直接陷入阻塞或者釋放 CPU 資源,而是開始利用循環,不停地嘗試獲取鎖,這個循環過程被形象地比喻為“自旋”,就像是線程在“自我旋轉”。相反,非自旋鎖的理念就是沒有自旋的過程,如果拿不到鎖就直接放棄,或者進行其他的處理邏輯,例如去排隊、陷入阻塞等。
可中斷鎖/不可中斷鎖
第 7 種分類是可中斷鎖和不可中斷鎖。在 Java 中,synchronized 關鍵字修飾的鎖代表的是不可中斷鎖,一旦線程申請了鎖,就沒有回頭路了,只能等到拿到鎖以后才能進行其他的邏輯處理。而我們的 ReentrantLock 是一種典型的可中斷鎖,例如使用 lockInterruptibly 方法在獲取鎖的過程中,突然不想獲取了,那么也可以在中斷之后去做其他的事情,不需要一直傻等到獲取到鎖才離開。
好了,本課時的內容就全部講完了,下一課時我將會從悲觀鎖和樂觀鎖開始詳細講解這一系列的鎖的具體概念和特點,下一課時見。