引用自:http://blog.iluckymeeting.com/2018/01/06/threadandlockone/
什么是鎖?
在多線程編程領域,基本上所有的編程模型都采用了“并發(fā)訪問串行處理”的策略,而方法就是給臨界資源加一把鎖。
為什么加鎖?
我們把多個線程競爭處理的資源稱為臨界資源(代碼塊、方法體等),根據“并發(fā)訪問串行處理”的策略,當一個線程獲得了臨界資源的使用權以后,其它線程必須等這個線程處理完以后才能通過競爭再次嘗試獲得臨界資源的使用權,為了保證臨界資源只由一個線程獲得,需要給臨界資源加鎖,線程要獲得臨界資源使用權必須先競爭鎖,一旦線程獲得了鎖,則其它線程就無法再獲得同一把鎖,必須等待這個鎖被解鎖釋放,才能再次競爭嘗試獲得這把鎖,一旦加鎖成功意味著線程獲得了臨界資源使用權。
如此這個問題就好理解了,要獲得臨界資源,就要先給資源加鎖,哪個線程加鎖成功,就代表著它暫時獲得了臨界資源的獨享權,別的線程需要等待臨界資源被解鎖才能再次嘗試競爭臨界資源。
加鎖的原理是什么?
要給臨界資源加鎖,常見的方式是使用synchronized關鍵字或Lock接口的各種實現(常用ReentrantLock),在本質上都是通過給某個對象實例加鎖實現臨界資源的鎖定,而給對象實例加鎖就用到了每個對象都有的一個數據結構對象頭。先來看一下對象頭的存儲結構:

對象頭有這么幾個特點:
- 如果這個對象是個數組,則對象頭將占用3個機器碼,如果不是數組,對象頭將占用2個機器碼(32bit)。
- 由于對象頭里存儲的是與對象數據無關的信息,為了提高空間利用率,對象頭的存儲結構不是固定的,當對象處于不同的狀態(tài)時,存儲結構有所區(qū)別
鎖的分類
如果從不同的用途和維度來看,會有多種不同的鎖定義,下面列舉幾種常見的鎖定義:
- 公平鎖、非公平鎖
- 自旋鎖、自適應自旋鎖
- 讀寫鎖
- 偏向鎖、輕量級鎖、重量級鎖
公平鎖、非公平鎖
所謂公平鎖就是線程獲得鎖的順序和線程請求鎖的順序相同,相反非公平鎖就是線程請求鎖的順序并不影響線程獲得鎖的順序。
要想保證線程獲得鎖的順序性,必然要花費一些資源去維護這個順序,所以公平鎖相比于非公平鎖效率有所下降。synchronized實現就是非公平鎖,ReentrantLock默認也是采用非公平鎖,不過可以指定使用公平鎖。
自旋鎖、自適應自旋鎖
在JDK1.6以前(這個記不清了),線程如果競爭鎖失敗會進入阻塞狀態(tài),當鎖被釋放后,重新進入喚醒狀態(tài)再次嘗試競爭鎖。線程在阻塞態(tài)和喚醒態(tài)之間切換需要通過操作系統(tǒng)的Metux Lock在用戶態(tài)和內核態(tài)之間切換,如果鎖被占用的時間很短,那么這個狀態(tài)切換所消耗的資源在整個處理過程中所占的比例就偏高,這樣就比較浪費了,為了解決這個問題,引入了自旋鎖。
所謂自旋鎖就是當線程競爭鎖失敗時,不進入阻塞狀態(tài),而是執(zhí)行一段無意義的計算空轉一下,JVM默認自旋10次,不過可以通過參數修改這個值。自旋鎖雖然提高了效率,但是通過分析發(fā)現,往往線程自旋失敗以后,鎖就被釋放了,也就是說如果線程能夠自旋12次就很有可能獲得鎖,于是又引入了JVM動態(tài)調整自旋次數的自適應自旋鎖。當一個線程競爭鎖成功以后,JVM認為它再次競爭鎖成功的概率比較大,于是JVM將它的自旋次數調高,相反如果一個線程競爭鎖失敗了,JVM認為它下次競爭成功的概率仍然較小,于是調小它的自旋次數,避免過多的無效自旋浪費計算資源。
讀寫鎖
有些場景下線程競爭鎖只是想做讀取操作,并不會修改共享數據,這種情況下如果使用排它鎖讓線程一個一個的去讀顯然不是最好的選擇,于是出現了讀寫鎖,ReadWriteLock就是讀寫鎖定義接口。通過讀寫鎖,多個線程可以同時給臨界資源加讀鎖,完成讀操作。
- 當臨界資源被加了讀鎖,則其它線程可以再次給臨界資源加讀鎖
- 當臨界資源被加了讀鎖,其它線程如果要加寫鎖,必須等讀鎖被釋放
- 當臨界資源被加了寫鎖,其它線程如果要加讀鎖或寫鎖,必須等待鎖被釋放
偏向鎖、輕量級鎖、重量級鎖
這是比較重要的一個鎖定義了,它們對資源的消耗及效率都是由低到高,偏向鎖遇到競爭時會膨脹成輕量級鎖,輕量級鎖在自旋失敗時會膨脹成重量級鎖,而且鎖的膨脹是單向不可逆的,它們的實現都需要通過對象頭結構,后面單獨寫一篇詳細介紹。