當前位置:首頁 > 公眾號精選 > 架構師社區(qū)
[導讀]什么是鎖 在計算機科學中,鎖(lock)或互斥(mutex)是一種同步機制,用于在有許多執(zhí)行線程的環(huán)境中強制對資源的訪問限制。鎖旨在強制實施互斥排他、并發(fā)控制策略。 ? ? ?鎖通常需要硬件支持才能有效實施。這種支持通常采取一個或多個原子指令的形式,如"test-an

什么是鎖

在計算機科學中,鎖(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)容,還沒關注的小伙伴,可以長按關注一下:

史上最全 Java 中各種鎖的介紹

長按訂閱更多精彩▼

史上最全 Java 中各種鎖的介紹

如有收獲,點個在看,誠摯感謝

免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權益,請及時聯(lián)系本站刪除。
關閉
關閉