史上最全的java裡面的鎖


什麼是鎖

在計算機科學中,鎖(lock)或互斥(mutex)是一種同步機制,用於在有許多執行線程的環境中強制對資源的訪問限制。鎖旨在強制實施互斥排他、併發控制策略。 鎖通常需要硬件支持才能有效實施。這種支持通常採取一個或多個原子指令的形式,如"test-and-set", "fetch-and-add" or "compare-and-swap"”。這些指令允許單個進程測試鎖是否空閒,如果空閒,則通過單個原子操作獲取鎖。

公平鎖

  • 定義:就是很公平,在併發環境中,每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果為空,或者當前線程線程是等待隊列的第一個,就佔有鎖,否則就會加入到等待隊列中,以後會按照FIFO的規則從隊列中取到自己。
  • 優點:所有的線程都能得到資源,不會餓死在隊列中。
  • 缺點:吞吐量會下降很多,隊列裡面除了第一個線程,其他的線程都會阻塞,cpu喚醒阻塞線程的開銷會很大。
  • 公平鎖獲取鎖例子
<code> 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    }/<code>

運行結果

<code> 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獲得了鎖/<code>

看到結果裡面獲得鎖的順序和線程啟動順序是一致的,這就是公平鎖。

非公平鎖

  • 定義:線程加鎖時直接嘗試獲取鎖,獲取不到就自動到隊尾等待。
  • 優點:非公平鎖性能高於公平鎖性能,非公平鎖能更充分的利用cpu的時間片,儘量的減少cpu空閒的狀態時間。
  • 缺點:可能導致隊列中間的線程一直獲取不到鎖或者長時間獲取不到鎖,導致餓死。
  • 非公平鎖列子:只需要將上面公平鎖的代碼改為new ReentrantLock(false);

    運行結果
<code> 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獲得了鎖/<code>

線程啟動順序是1、0、2、3、 4、 8 、7 、6 、5 、9,獲得鎖的順序卻是1 、0 、5 、2 、3 、4 、8 、7 、6 、9,這就是非公平鎖,它不保證先排隊嘗試去獲取鎖的線程一定能先拿到鎖。

重入鎖

  • 定義: - 可重入鎖指的是可重複可遞歸調用的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個對象或者class),這樣的鎖就叫做可重入鎖。ReentrantLock和synchronized都是可重入鎖
    下面是一個synchronized重入鎖的列子:
<code> 1public class ReentrantLockTest {
 2
 3
 4    public static void main(String[] args){
 5        for (int i = 0; i  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}/<code>

輸出:

<code>1Thread-1
2Thread-1
3Thread-0
4Thread-0/<code>

A方法和B方法同時輸出了線程名稱,表明即使遞歸使用synchronized也沒有發生死鎖,證明其是可重入的。

讀寫鎖

讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。

  • 與傳統鎖不同的是讀寫鎖的規則是可以共享讀,但只能一個寫,總結起來為:讀讀不互斥,讀寫互斥,寫寫互斥,而一般的獨佔鎖是:讀讀互斥,讀寫互斥,寫寫互斥,而場景中往往讀遠遠大於寫,讀寫鎖就是為了這種優化而創建出來的一種機制。注意是讀遠遠大於寫,一般情況下獨佔鎖的效率低來源於高併發下對臨界區的激烈競爭導致線程上下文切換。因此當併發不是很高的情況下,讀寫鎖由於需要額外維護讀鎖的狀態,可能還不如獨佔鎖的效率高。因此需要根據實際情況選擇使用。
  • Java裡面ReentrantReadWriteLock讀寫鎖特性
    公平選擇性: 支持非公平(默認)和公平的鎖獲取方式,吞吐量還是非公平優於公平。
    重進入: 讀鎖和寫鎖都支持線程重進入。
    鎖降級:
    鎖降級是指把持住(當前擁有的)寫鎖,再獲取到讀鎖,隨後釋放(先前擁有的)寫鎖的過程。

樂觀鎖、悲觀鎖

  • 樂觀鎖:樂觀鎖總是認為不存在併發問題,每次去取數據的時候,總認為不會有其他線程對數據進行修改,因此不會上鎖。但是在更新時會判斷其他線程在這之前有沒有對數據進行修改,一般會使用“數據版本機制”或“CAS操作”來實現。
  • 悲觀鎖: 悲觀鎖認為對於同一個數據的併發操作,一定會發生修改的,哪怕沒有修改,也會認為修改。因此對於同一份數據的併發操作,悲觀鎖採取加鎖的形式。悲觀的認為,不加鎖併發操作一定會出問題。典型的數據庫的查詢 for update。 在對任意記錄進行修改前,先嚐試為該記錄加上排他鎖(exclusive locking)。 如果加鎖失敗,說明該記錄正在被修改,那麼當前查詢可能要等待或者拋出異常。具體響應方式由開發者根據實際需要決定。 如果成功加鎖,那麼就可以對記錄做修改,事務完成後就會解鎖了。期間如果有其他對該記錄做修改或加排他鎖的操作,都會等待我們解鎖或直接拋出異常。

分段鎖

分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對jdk1.7 及以前的ConcurrentHashMap而言,其併發的實現就是通過分段鎖的形式來實現高效的併發操作。分段鎖的設計目的是細化鎖的粒度,當操作不需要更新整個數組的時候,就僅僅針對數組中的一項進行加鎖操作。

自旋鎖

在Java中,自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是循環會消耗CPU。

偏向鎖、輕量級鎖、重量級鎖

 這三種鎖是指鎖的狀態,並且是針對Synchronized。在Java 5通過引入鎖升級的機制來實現高效Synchronized。這三種鎖的狀態是通過對象監視器在對象頭中的字段來表明的。 

  • 偏向鎖:是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。降低獲取鎖的代價。
  • 輕量級鎖:是指當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。
  • 重量級鎖:是指當鎖為輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓他申請的線程進入阻塞,性能降低。

獨享鎖、共享鎖

  •  獨享鎖是指該鎖一次只能被一個線程所持有。
  •  共享鎖是指該鎖可被多個線程所持有。對於Java ReentrantLock而言,其是獨享鎖。但是對於Lock的另一個實現類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨享鎖。 讀鎖的共享鎖可保證併發讀是非常高效的,讀寫,寫讀,寫寫的過程是互斥的。獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。對於Synchronized而言,當然是獨享鎖。


分享到:


相關文章: