史上最全 Java 中各種鎖的介紹
掃描二維碼
隨時隨地手機看文章
什么是鎖
在計算機科學中,鎖(lock)或互斥(mutex)是一種同步機制,用于在有許多執(zhí)行線程的環(huán)境中強制對資源的訪問限制。鎖旨在強制實施互斥排他、并發(fā)控制策略。
鎖通常需要硬件支持才能有效實施。這種支持通常采取一個或多個原子指令的形式,如"test-and-set", "fetch-and-add" or "compare-and-swap"”。這些指令允許單個進程測試鎖是否空閑,如果空閑,則通過單個原子操作獲取鎖。
公平鎖
定義:就是很公平,在并發(fā)環(huán)境中,每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果為空,或者當前線程線程是等待隊列的第一個,就占有鎖,否則就會加入到等待隊列中,以后會按照FIFO的規(guī)則從隊列中取到自己。
優(yōu)點:所有的線程都能得到資源,不會餓死在隊列中。
缺點:吞吐量會下降很多,隊列里面除了第一個線程,其他的線程都會阻塞,cpu喚醒阻塞線程的開銷會很大。
公平鎖獲取鎖例子
1 /**
2 * true 表示 ReentrantLock 的公平鎖
3 */
4 private ReentrantLock lock = new ReentrantLock(true);
5
6 public void testFail(){
7 try {
8 lock.lock();
9 System.out.println(Thread.currentThread().getName() +"獲得了鎖");
10 }finally {
11 lock.unlock();
12 }
13 }
14 public static void main(String[] args) {
15 FairLockTest fairLock = new FairLockTest();
16 Runnable runnable = () -> {
17 System.out.println(Thread.currentThread().getName()+"啟動");
18 fairLock.testFail();
19 };
20 Thread[] threadArray = new Thread[10];
21 for (int i=0; i<10; i++) {
22 threadArray[i] = new Thread(runnable);
23 }
24 for (int i=0; i<10; i++) {
25 threadArray[i].start();
26 }
27 }
運行結果
1Thread-1啟動
2Thread-1獲得了鎖
3Thread-3啟動
4Thread-3獲得了鎖
5Thread-5啟動
6Thread-5獲得了鎖
7Thread-2啟動
8Thread-2獲得了鎖
9Thread-4啟動
10Thread-4獲得了鎖
11Thread-6啟動
12Thread-6獲得了鎖
13Thread-10啟動
14Thread-8啟動
15Thread-10獲得了鎖
16Thread-9啟動
17Thread-7啟動
18Thread-8獲得了鎖
19Thread-9獲得了鎖
20Thread-7獲得了鎖
看到結果里面獲得鎖的順序和線程啟動順序是一致的,這就是公平鎖。
非公平鎖
定義:線程加鎖時直接嘗試獲取鎖,獲取不到就自動到隊尾等待。
優(yōu)點:非公平鎖性能高于公平鎖性能,非公平鎖能更充分的利用cpu的時間片,盡量的減少cpu空閑的狀態(tài)時間。
缺點:可能導致隊列中間的線程一直獲取不到鎖或者長時間獲取不到鎖,導致餓死。
非公平鎖列子:只需要將上面公平鎖的代碼改為
new ReentrantLock(false);
運行結果
1Thread-1啟動
2Thread-0啟動
3Thread-2啟動
4Thread-3啟動
5Thread-4啟動
6Thread-8啟動
7Thread-7啟動
8Thread-6啟動
9Thread-1獲得了鎖
10Thread-0獲得了鎖
11Thread-5啟動
12Thread-5獲得了鎖
13Thread-2獲得了鎖
14Thread-3獲得了鎖
15Thread-4獲得了鎖
16Thread-8獲得了鎖
17Thread-7獲得了鎖
18Thread-6獲得了鎖
19Thread-9啟動
20Thread-9獲得了鎖
線程啟動順序是1、0、2、3、 4、 8 、7 、6 、5 、9,獲得鎖的順序卻是1 、0 、5 、2 、3 、4 、8 、7 、6 、9,這就是非公平鎖,它不保證先排隊嘗試去獲取鎖的線程一定能先拿到鎖。
重入鎖
定義:- 可重入鎖指的是可重復可遞歸調(diào)用的鎖,在外層使用鎖之后,在內(nèi)層仍然可以使用,并且不發(fā)生死鎖(前提得是同一個對象或者class),這樣的鎖就叫做可重入鎖。ReentrantLock和synchronized都是可重入鎖
下面是一個synchronized重入鎖的列子:
1public class ReentrantLockTest {
2
3
4 public static void main(String[] args){
5 for (int i = 0; i < 10; i++) {
6 new Thread(() -> A()).start();
7 }
8 }
9 public static synchronized void A(){
10 System.out.println(Thread.currentThread().getName());
11 B();
12 }
13 public static synchronized void B(){
14 System.out.println(Thread.currentThread().getName());
15 }
16}
輸出:
1Thread-1
2Thread-1
3Thread-0
4Thread-0
A方法和B方法同時輸出了線程名稱,表明即使遞歸使用synchronized也沒有發(fā)生死鎖,證明其是可重入的。
讀寫鎖
百度百科定義的讀寫鎖是:
讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。
與傳統(tǒng)鎖不同的是讀寫鎖的規(guī)則是可以共享讀,但只能一個寫,總結起來為:讀讀不互斥,讀寫互斥,寫寫互斥,而一般的獨占鎖是:讀讀互斥,讀寫互斥,寫寫互斥,而場景中往往讀遠遠大于寫,讀寫鎖就是為了這種優(yōu)化而創(chuàng)建出來的一種機制。注意是讀遠遠大于寫,一般情況下獨占鎖的效率低來源于高并發(fā)下對臨界區(qū)的激烈競爭導致線程上下文切換。因此當并發(fā)不是很高的情況下,讀寫鎖由于需要額外維護讀鎖的狀態(tài),可能還不如獨占鎖的效率高。因此需要根據(jù)實際情況選擇使用。
Java里面ReentrantReadWriteLock讀寫鎖特性
公平選擇性: 支持非公平(默認)和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平。
重進入: 讀鎖和寫鎖都支持線程重進入。
鎖降級: 鎖降級是指把持住(當前擁有的)寫鎖,再獲取到讀鎖,隨后釋放(先前擁有的)寫鎖的過程。
樂觀鎖、悲觀鎖
樂觀鎖:樂觀鎖總是認為不存在并發(fā)問題,每次去取數(shù)據(jù)的時候,總認為不會有其他線程對數(shù)據(jù)進行修改,因此不會上鎖。但是在更新時會判斷其他線程在這之前有沒有對數(shù)據(jù)進行修改,一般會使用“數(shù)據(jù)版本機制”或“CAS操作”來實現(xiàn)。
悲觀鎖: 悲觀鎖認為對于同一個數(shù)據(jù)的并發(fā)操作,一定會發(fā)生修改的,哪怕沒有修改,也會認為修改。因此對于同一份數(shù)據(jù)的并發(fā)操作,悲觀鎖采取加鎖的形式。悲觀的認為,不加鎖并發(fā)操作一定會出問題。典型的數(shù)據(jù)庫的查詢 for update。
在對任意記錄進行修改前,先嘗試為該記錄加上排他鎖(exclusive locking)。
如果加鎖失敗,說明該記錄正在被修改,那么當前查詢可能要等待或者拋出異常。具體響應方式由開發(fā)者根據(jù)實際需要決定。
如果成功加鎖,那么就可以對記錄做修改,事務完成后就會解鎖了。期間如果有其他對該記錄做修改或加排他鎖的操作,都會等待我們解鎖或直接拋出異常。
分段鎖
分段鎖其實是一種鎖的設計,并不是具體的一種鎖,對jdk1.7 及以前的ConcurrentHashMap而言,其并發(fā)的實現(xiàn)就是通過分段鎖的形式來實現(xiàn)高效的并發(fā)操作。分段鎖的設計目的是細化鎖的粒度,當操作不需要更新整個數(shù)組的時候,就僅僅針對數(shù)組中的一項進行加鎖操作。
自旋鎖
在Java中,自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是循環(huán)會消耗CPU。
偏向鎖、輕量級鎖、重量級鎖
這三種鎖是指鎖的狀態(tài),并且是針對Synchronized。在Java 5通過引入鎖升級的機制來實現(xiàn)高效Synchronized。這三種鎖的狀態(tài)是通過對象監(jiān)視器在對象頭中的字段來表明的。
偏向鎖:是指一段同步代碼一直被一個線程所訪問,那么該線程會自動獲取鎖。降低獲取鎖的代價。
輕量級鎖:是指當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。
重量級鎖:是指當鎖為輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續(xù)下去,當自旋一定次數(shù)的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓他申請的線程進入阻塞,性能降低。
獨享鎖、共享鎖
獨享鎖是指該鎖一次只能被一個線程所持有。
共享鎖是指該鎖可被多個線程所持有。
對于Java ReentrantLock而言,其是獨享鎖。但是對于Lock的另一個實現(xiàn)類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨享鎖。 讀鎖的共享鎖可保證并發(fā)讀是非常高效的,讀寫,寫讀,寫寫的過程是互斥的。獨享鎖與共享鎖也是通過AQS來實現(xiàn)的,通過實現(xiàn)不同的方法,來實現(xiàn)獨享或者共享。對于Synchronized而言,當然是獨享鎖。參考文章
https://blog.csdn.net/qiuwenjie123/article/details/79950532
https://segmentfault.com/q/1010000009659039
https://blog.csdn.net/qq_43519310/article/details/100107346
https://blog.csdn.net/u010648018/article/details/79750608
https://www.cnblogs.com/hustzzl/p/9343797.html
http://ifeve.com/locks/
http://ifeve.com/locks/
特別推薦一個分享架構+算法的優(yōu)質(zhì)內(nèi)容,還沒關注的小伙伴,可以長按關注一下:
長按訂閱更多精彩▼
如有收獲,點個在看,誠摯感謝
免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!