如何理解Java中眼花繚亂的各種并發鎖?

在互聯網公司面試中,很多小伙伴都被問到過關于鎖的問題 。今天,我給大家一次性把Java并發鎖的全家桶徹底講明白 。包括互斥鎖、讀寫鎖、重入鎖、公平鎖、悲觀鎖、自旋鎖、偏向鎖等等等等 。視頻有點長,大家一定要全部看完,保證你會醍醐灌頂 。
1、鎖的由來在并發編程中 , 經常會遇到兩個以上的線程訪問同一個共享變量,當同時對共享變量進行讀寫操作時,就會產生數據不一致的情況 。

如何理解Java中眼花繚亂的各種并發鎖?

文章插圖
隨著線程并發技術的發展,在多線程環境中,對線程訪問資源的限制也越來越多 。為了保證資源獲取的有序性和占用性 , 都是通過并發鎖來控制的 。
2、鎖的應用場景下面,我根據個人經驗以及并發場景下線程的處理邏輯,總結為以下7個場景,不同場景使用不同的鎖 。
1)某個線程是否鎖住同步資源的情況如果要鎖住同步資源則使用悲觀鎖,不鎖住同步資源使用樂觀鎖 。所謂悲觀鎖,就是每次拿數據的時候都認為會有別人修改,所以在讀數據的時候都會上鎖 , 其他線程數據就會阻塞 , 直到拿到鎖 。
如何理解Java中眼花繚亂的各種并發鎖?

文章插圖
舉個例子,假設廁所只有一個坑位,悲觀鎖就是上廁所會第一時間把門反鎖上,這樣其他人上廁所只能在門外等候,這就是阻塞 。
如何理解Java中眼花繚亂的各種并發鎖?

文章插圖
而樂觀鎖就是開著門,當然在這個場景下一般也不會這么做 。所以,樂觀鎖,就是每次拿數據的時候都假設為別人不會修改,所以不會上鎖;只是在更新數據的時候去判斷之前有沒有別的線程更新了這個數據 。如果這個數據沒有被更新,當前線程將自己修改的數據成功寫入 。如果數據已經被其他線程更新了,要么報錯,要么自動重試 。
如何理解Java中眼花繚亂的各種并發鎖?

文章插圖
樂觀鎖與悲觀鎖是一種廣義上的概念,沒有誰優誰劣 。樂觀鎖適用于寫少讀多的場景,因為不用上鎖、釋放鎖,省去了鎖的開銷,從而提升了吞吐量 。而悲觀鎖適用于寫多讀少的場景,因為線程間競爭激勵,如果使用樂觀鎖會導致線程不斷進行重試,這樣反而還降低了性能 。
2)多個線程是否共享一把鎖的情況如果在并發情況下 , 多個線程共享一把鎖就是使用共享鎖 , 如果不能共享一把鎖就是排它鎖或者叫獨占鎖、獨享鎖 。共享鎖是指鎖可被多個線程所持有 。如果一個線程對數據加上共享鎖后,那么其他線程只能對數據再加共享鎖,不能加獨占鎖 。獲得共享鎖的線程只能讀數據,不能修改數據 。
如何理解Java中眼花繚亂的各種并發鎖?

文章插圖
在 JDK 中 ReentrantReadWriteLock 就是一種共享鎖 。而獨占鎖是指鎖一次只能被一個線程所持有 。如果一個線程對數據加上排他鎖后 , 那么其他線程不能再對該數據加任何類型的鎖 。獲得獨占鎖的線程即能讀數據又能修改數據 。
如何理解Java中眼花繚亂的各種并發鎖?

文章插圖
JDK中的synchronized和J.U.C(java.util.concurrent)包中Lock的實現類都是獨占鎖 。另外 , 互斥鎖是獨占鎖的一種常規實現 , 是指某一資源同時只允許一個訪問者對其進行訪問 , 具有唯一性和排它性 。
如何理解Java中眼花繚亂的各種并發鎖?

文章插圖
互斥鎖一次只能一個線程擁有互斥鎖,其他線程只有等待 。而讀寫鎖是共享鎖的一種具體實現 。讀寫鎖管理一組鎖,一個是只讀的鎖,一個是寫鎖 。讀鎖可以在沒有寫鎖的時候被多個線程同時持有,而寫鎖是獨占的 。寫鎖的優先級要高于讀鎖,一個獲得了讀鎖的線程必須能看到前一個釋放的寫鎖所更新的內容 。讀寫鎖相比于互斥鎖并發程度更高,每次只有一個寫線程,但是同時可以有多個線程并發讀 。
如何理解Java中眼花繚亂的各種并發鎖?

文章插圖
在 JDK 中定義了一個讀寫鎖的接口ReadWriteLock,如源碼所示:
public interface ReadWriteLock {/* 獲取讀鎖 */Lock readLock();/* 獲取寫鎖 */Lock writeLock();}ReentrantReadWriteLock 實現了ReadWriteLock接口,ReentrantReadWriteLock 支持鎖降級不支持鎖升級,可以由寫鎖降為讀鎖 。
3)多個線程競爭時是否要排隊的情況多個線程競爭排隊獲取鎖的情況,使用公平鎖,如果 , 使用非公平鎖 。所謂公平鎖是指多個線程按照申請鎖的順序來獲取鎖 , 這里類似排隊買票,先來的人先買,后來的人在隊尾排著,這是公平的 。

推薦閱讀