鎖匯總
- 2019 年 10 月 4 日
- 筆記
個人部落格:https://suveng.github.io/blog/
各種鎖的匯總
在學習中經常會接觸到各種各樣的鎖,行級鎖、表級鎖、頁級鎖、悲觀鎖、樂觀鎖、重入鎖、共享鎖、排他鎖 等等等等。今天就來總結一下這些各種各樣的鎖。
目前收集到的:行級鎖、表級鎖、頁級鎖、悲觀鎖、樂觀鎖、重入鎖、共享鎖、排他鎖、公平鎖、非公平鎖、自旋鎖、互斥鎖、無鎖、偏向鎖、輕量級鎖、重量級鎖
- 面向多層面的鎖思想:悲觀鎖、樂觀鎖
- 資料庫方向的鎖:行級鎖、表級鎖、頁級鎖、共享鎖(讀鎖、S鎖)、排他鎖(寫鎖、X鎖)、更新鎖
- 多執行緒方向的鎖:重入鎖、公平鎖、非公平鎖、自旋鎖、互斥鎖
- 執行緒在JVM與作業系統層面的鎖:無鎖、偏向鎖、輕量級鎖、重量級鎖
按粒度分:行級鎖、表級鎖、頁級鎖
按鎖級別分:共享鎖(讀鎖、S鎖)、排他鎖(寫鎖、X鎖)、更新鎖
按使用方式分:樂觀鎖、悲觀鎖
按搶佔方式分:公平鎖、非公平鎖
按執行緒用戶態與內核態切換狀態分:無鎖、偏向鎖、輕量級鎖、重量級鎖
面向多層面的鎖思想
樂觀鎖
樂觀鎖假設認為數據一般情況下不會造成衝突,所以在數據進行提交更新的時候,才會正式對數據的衝突與否進行檢測,如果發現衝突了,則讓返回用戶錯誤的資訊,讓用戶決定如何去做。可以通過版本號或時間戳的方式實現。 特點:樂觀並發控制相信事務之間的數據競爭的概率是比較小的,因此儘可能直接做下去,直到提交的時候才去鎖定,所以不會產生任何鎖和死鎖。但如果直接簡單這麼做,還是有可能會遇到不可預期的結果,例如兩個事務都讀取了資料庫的某一行,經過修改以後寫回資料庫,這時就遇到了問題。
悲觀鎖
悲觀鎖,正如其名,它指的是對數據被外界,因此,在整個數據處理過程中,將數據處於鎖定狀態。 特點:悲觀並發控制實際上是「先取鎖再訪問」的保守策略,為數據處理的安全提供了保證。但是在效率方面,處理加鎖的機制會讓資料庫產生額外的開銷,還有增加產生死鎖的機會;另外,在只讀型事務處理中由於不會產生衝突,也沒必要使用鎖,這樣做只能增加系統負載;還有會降低了並行性,一個事務如果鎖定了某行數據,其他事務就必須等待該事務處理完才可以處理那行數據。
資料庫方向的鎖
共享鎖(讀鎖、S鎖)
又稱讀鎖,若事務T對數據對象A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。
排他鎖(寫鎖、X鎖)
又稱寫鎖。若事務T對數據對象A加上X鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的鎖。這保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A。
更新鎖
更新 (U) 鎖可以防止通常形式的死鎖。一般更新模式由一個事務組成,此事務讀取記錄,獲取資源(頁或行)的共享 (S) 鎖,然後修改行,此操作要求鎖轉換為排它 (X) 鎖。如果兩個事務獲得了資源上的共享模式鎖,然後試圖同時更新數據,則一個事務嘗試將鎖轉換為排它 (X) 鎖。共享模式到排它鎖的轉換必須等待一段時間,因為一個事務的排它鎖與其它事務的共享模式鎖不兼容;發生鎖等待。第二個事務試圖獲取排它 (X) 鎖以進行更新。由於兩個事務都要轉換為排它 (X) 鎖,並且每個事務都等待另一個事務釋放共享模式鎖,因此發生死鎖。 若要避免這種潛在的死鎖問題,請使用更新 (U) 鎖。一次只有一個事務可以獲得資源的更新 (U) 鎖。如果事務修改資源,則更新 (U) 鎖轉換為排它 (X) 鎖。否則,鎖轉換為共享鎖。
行級鎖
行級鎖是Mysql中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。行級鎖能大大減少資料庫操作的衝突。其加鎖粒度最小,但加鎖的開銷也最大。 特點:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,並發度也最高。
表級鎖
表級鎖是MySQL中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少,被大部分MySQL引擎支援。最常使用的MYISAM與INNODB都支援表級鎖定。 特點:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發出鎖衝突的概率最高,並發度最低。
頁級鎖
頁級鎖是MySQL中鎖定粒度介於行級鎖和表級鎖中間的一種鎖。表級鎖速度快,但衝突多,行級衝突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。 特點:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,並發度一般
多執行緒方向的鎖
重入鎖
重進入是指任意執行緒在獲取到鎖之後,再次獲取該鎖而不會被該鎖所阻塞。關聯一個執行緒持有者+計數器,重入意味著鎖操作的顆粒度為「執行緒」。 重入鎖的實現方式:每個鎖關聯一個執行緒持有者和計數器,當計數器為0時表示該鎖沒有被任何執行緒持有,那麼任何執行緒都可能獲得該鎖而調用相應的方法;當某一執行緒請求成功後,JVM會記下鎖的持有執行緒,並且將計數器置為1;此時其它執行緒請求該鎖,則必須等待;而該持有鎖的執行緒如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增;當執行緒退出同步程式碼塊時,計數器會遞減,如果計數器為0,則釋放該鎖
公平鎖
Java的執行緒從 Runnable 狀態轉向 Running 狀態的過程中需要獲取時間片,公平鎖按進入順序依次分配時間片,即先來先服務 FIFO 的思想。
非公平鎖
非公平鎖相對於公平鎖,主要體現的是隨機性。Java的執行緒從 Runnable 狀態轉向 Running 狀態時實行搶佔機制獲取時間片。
自旋鎖
自旋鎖的核心:不放棄時間片。執行緒獲取不到鎖,就會被阻塞掛起,等其他執行緒釋放鎖的時候,才被喚醒起來。執行緒掛起和喚醒是需要轉入到內核態完成的,這些操作對系統的並發性能會帶來影響。其實有時候執行緒雖然沒法立刻獲取到鎖,但是也可能很快就會獲取到鎖。JVM採用了一種叫自旋鎖的機制,讓獲取不到鎖的執行緒執行一個空的循環,一段時間後,如果還是沒法獲取鎖,執行緒才會被掛起。 如果鎖競爭不嚴重的情況下,且任務執行時間不長,那麼可以嘗試使用自旋鎖。 缺點:如果執行緒執行的任務需要非常長的時間,或者執行緒對共享數據的競爭相當激烈,那麼使用自旋鎖的效率就很低。因為自旋的過程中,一直無法獲取到鎖,一直在白白的浪費CPU的資源。
互斥鎖
保證在同一時刻只有一個執行緒對其進行操作。比如最常見的 synchronized。
執行緒在JVM與作業系統層面的鎖
先了解一些關於執行緒在 JVM 與作業系統層面的基礎知識
- java執行緒阻塞的代價 java的執行緒是映射到作業系統原生執行緒之上的,如果要阻塞或喚醒一個執行緒就需要作業系統介入,需要在戶態與核心態之間切換,這種切換會消耗大量的系統資源,因為用戶態與內核態都有各自專用的記憶體空間,專用的暫存器等,用戶態切換至內核態需要傳遞給許多變數、參數給內核,內核也需要保護好用戶態在切換時的一些暫存器值、變數等,以便內核態調用結束後切換回用戶態繼續工作。 如果執行緒狀態切換是一個高頻操作時,這將會消耗很多CPU處理時間; 如果對於那些需要同步的簡單的程式碼塊,獲取鎖掛起操作消耗的時間比用戶程式碼執行的時間還要長,這種同步策略顯然非常糟糕的。 synchronized會導致爭用不到鎖的執行緒進入阻塞狀態,所以說它是java語言中一個重量級的同步操縱,被稱為重量級鎖,為了緩解上述性能問題,JVM從1.5開始,引入了輕量鎖與偏向鎖,默認啟用了自旋鎖,他們都屬於樂觀鎖。
明確java執行緒切換的代價,是理解java中各種鎖的優缺點的基礎之一。
- markword


- 鎖的升級 Java SE1.6為了減少獲得鎖和釋放鎖所帶來的性能消耗,引入了「偏向鎖」和「輕量級鎖」,所以在Java SE1.6里鎖一共有四種狀態,無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,它會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。 偏向鎖 偏向鎖(Biased Lock)主要解決無競爭下的鎖性能問題。偏向鎖,顧名思義,它會偏向於第一個訪問鎖的執行緒,如果在運行過程中,同步鎖只有一個執行緒訪問,不存在多執行緒爭用的情況,則執行緒是不需要觸發同步的,這種情況下,就會給執行緒加一個偏向鎖。 如果在運行過程中,遇到了其他執行緒搶佔鎖,則持有偏向鎖的執行緒會被掛起,JVM會消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖。 輕量級鎖 輕量級鎖是由偏向鎖升級來的,偏向鎖運行在一個執行緒進入同步塊的情況下,當第二個執行緒加入鎖爭用的時候,偏向鎖就會升級為輕量級鎖。

重量級鎖 重量級鎖,就是讓爭搶鎖的執行緒從用戶態轉換成內核態。讓cpu藉助作業系統進行執行緒協調。 三種鎖的對比
