參考:https://mp.weixin.qq.com/s/9zRmjH5Bgzo-EDIzZ5C7Hg
操作系統中的鎖分為兩大類:悲觀鎖和樂觀鎖。
1. 悲觀鎖
悲觀鎖,Pessimistic Lock,即這種鎖的“想法”很悲觀——方法執行如果不加鎖就會出事,所以操作必須上鎖,一個一個的來。
其中重量級鎖、自旋鎖和自適應自旋鎖屬於悲觀鎖。
1.1 重量級鎖
當進入一個同步、線程安全的方法時,需要先獲得該方法的鎖,而退出這個方法時,則需要釋放鎖。如果線程A獲取不到該鎖,則意味着該方法有別的線程在執行,這時線程A會馬上進入阻塞狀態,直到持有鎖的線程釋放鎖,才會從阻塞狀態被喚醒,再嘗試去獲取該方法的鎖。
重量級鎖的特點是:獲取不到鎖,馬上進入阻塞狀態
由於重量級鎖的特點,導致了它的效率有時候會很慢。試想,當線程A發現鎖被占用,立即進入阻塞狀態,之后的0.0001秒,這個鎖便被釋放了,那么它又會從阻塞狀態進入運行狀態。我們都知道,線程從運行狀態進入阻塞狀態,需要保存線程的執行狀態、上下文等數據,以及設計到用戶態到內核態的轉換,非常耗時,同樣從阻塞狀態到運行狀態也是一樣的道理。因此重量級鎖這個馬上進入阻塞狀態的特點,往往會耗費很多的時間在線程的狀態轉換中。
1.2 自旋鎖
自旋鎖是在重量級鎖的基礎上,做出了一些改進,它在線程判斷方法有別的線程執行之后,不會立馬進入阻塞狀態,而是等待一段時間,也就是在一段固定的循環時間內,看看這個鎖有沒有被釋放。如果一直沒有被釋放,線程才會進入阻塞狀態。
自旋鎖的特點是:獲取不到鎖,等一段固定時間,再進入阻塞狀態
1.3自適應自旋鎖
自適應自旋鎖是在自旋鎖的基礎上,對一段固定時間作出了一些調整:不需要人為去指定這一段固定時間究竟是多久,而是根據線程最近獲得鎖的狀態來調整循環次數,盡量去減少等待時間,從而減少CPU的消耗。
2. 樂觀鎖
樂觀鎖,Optimistic Lock,即這種鎖的“想法”很樂觀——方法執行不用加鎖,要是出現了沖突再想辦法去解決。
不加鎖,就應該用其他的方法去控制方法的同步和線程安全,也就是CAS機制。
2.1 CAS機制
CAS,Compare and Swap,比較並替換。CAS機制中采用了3個基本操作數,分別是內存地址V、舊預期值A、新值B。當線程在更新一個變量的時候,只有當變量的舊預期值A和內存地址V當中的實際值相同時,才會將內存地址V對應的值修改為B。
這里有一個例子來說明一下CAS機制:
有兩個線程A、B,都想要將內存地址V當中的值加1,內存地址V當中的初始值為10。
1、對於線程A來說,舊預期值A=10,新值B=11;
2、而當線程A提交更新之前,線程B搶先一步,將內存地址V當中的值加1變成了11;
3、此時,線程A開始提交更新,將舊預期值A與內存地址V當中的值進行比較,發現不等,提交失敗;
4、線程A重新嘗試獲取舊預期值A=11,新值B=12,該重新嘗試的過程被稱為自旋;
5、線程A提交更新,舊預期值A與內存地址V當中的值相同,提交更新,此時內存地址V當中的值更新為B,也就是12。
CAS的缺點:
1) CPU開銷過大:在並發量比較高的情況下,如果許多線程反復嘗試更新某一個變量,卻又一直更新不成功,循環往復,會給CPU帶來很到的壓力。
2) 不能保證代碼塊的原子性:CAS機制所保證的只是一個變量的原子性操作,而不能保證整個代碼塊的原子性。比如需要保證3個變量共同進行原子性的更新,就不得不使用synchronized了。
3) ABA問題:這是CAS機制最大的問題所在。通常會采取加入版本號,從而有效的解決ABA問題。
其中輕量級鎖和偏向鎖屬於樂觀鎖,並使用CAS機制來對方法進行相應的標記,從而保證方法的同步和線程安全。
2.2 輕量級鎖
輕量級鎖會認為,很少有線程剛好也來執行相同的方法,所以,當進入一個方法的時候根本不用加鎖,只需要做一個標記,也就是一個變量,來記錄此時該方法是否有人在執行。當該方法沒有在執行的時候,線程進入該方法,采用CAS機制,將方法的狀態標記為正在執行,當退出方法的時候,再將狀態標記為沒有在執行。使用CAS機制來改變狀態,是因為對狀態的改變並不是原子操作,所以會使用CAS機制來保證操作的原子性。
輕量級鎖適合很少出現多個線程競爭一個鎖的情況,即多個線程總是錯開時間來獲取鎖的情況。
2.3 偏向鎖
如果線程是首次執行該方法,那么便會采用CAS機制將其標記為有人在執行,同時會將線程ID記錄進去,以標記是哪個線程正在執行。而當線程退出該方法的時候,不會改變其狀態,而是直接退出,因為其默認除了本線程,其他線程並不會執行該方法。這樣當這個線程想要再次執行該方法的時候,會判斷其狀態,如果已經被標記為有人在執行並且線程的ID是自己,那么就直接執行方法。
偏向鎖適用於始終只有一個線程在執行一個方法的情況。
總結
兩類鎖並沒有孰好孰壞之分,因為其不同的“想法”,分別會有着它們最合適的應用條件和場景。由於悲觀鎖的“想法”比較悲觀,認為不加鎖就會出錯,因此會阻塞事務。一般來說悲觀鎖會應用於經常發生沖突的時候,通俗來講可以理解為寫多讀少的情況。而樂觀鎖由於“想法”比較樂觀,認為沒有必要加鎖,因此往往會回滾重試。一般來說樂觀鎖會應用於沖突較少發生的時候,通俗理解為讀多寫少的情況,這樣就可以省去鎖的開銷,從而加大系統的吞吐量。