1. 程式人生 > >後端---Java中的公平鎖和非公平鎖實現詳解

後端---Java中的公平鎖和非公平鎖實現詳解

Java中的公平鎖和非公平鎖實現詳解

 

在Java中實現鎖的方式有兩種,一種是使用Java自帶的關鍵字synchronized對相應的類或者方法以及程式碼塊進行加鎖,另一種是ReentrantLock,前者只能是非公平鎖,而後者是預設非公平但可實現公平的一把鎖。

ReentrantLock的實現是基於其內部類FairSync(公平鎖)NonFairSync(非公平鎖)實現的。 其可重入性是基於Thread.currentThread()實現的: 如果當前執行緒已經獲得了執行序列中的鎖, 那執行序列之後的所有方法都可以獲得這個鎖。

在本文將集中對ReentrantLock以及它實現公平鎖和非公平鎖的方式進行講解:

首先我們來看一段ReentrantLock的原始碼,有助於我們等會對非公平和公平鎖的理解:

 

public class ReentrantLockExample {
	int a=0;
	ReentrantLock lock= new ReentrantLock();
	public void write() {
		lock.lock();                // 獲取鎖
		try {
			a++;
		}finally  {
			lock.unlock();          // 釋放鎖
		}
	}
	
	public void reader() {
		lock.lock();                // 獲取鎖
		try {
			int i=a;
			...
		} finally {
			lock.unlock();          // 釋放鎖
		}
	}
}

ReentrantLock的實現依賴於Java同步器框架AbstractQueuedSynchronizer(AQS)。AQS使用一個整形的volatile變數state來維護同步狀態,這個volatile變數是實現ReentrantLock的關鍵。我再看一下下面ReentrantLock的類圖

公平鎖:

公平和非公平鎖的佇列都基於鎖內部維護的一個雙向連結串列,表結點Node的值就是每一個請求當前鎖的執行緒。公平鎖則在於每次都是依次從隊首取值。
鎖的實現方式是基於如下幾點: 
表結點Node和狀態state的volatile關鍵字。
sum.misc.Unsafe.compareAndSet的原子操作。

非公平鎖:

在等待鎖的過程中, 如果有任意新的執行緒妄圖獲取鎖,都是有很大的機率直接獲取到鎖的。
ReentrantLock鎖都不會使得執行緒中斷,除非開發者自己設定了中斷位。 
ReentrantLock獲取鎖裡面有看似自旋的程式碼,但是它不是自旋鎖。 
ReentrantLock公平與非公平鎖都是屬於排它鎖。

ReentrantLock的可重入性分析

這裡有一篇對鎖介紹甚為詳細的文章 朱小廝的部落格-Java中的鎖.

synchronized的可重入性

參考這篇文章: Java內建鎖synchronized的可重入性

 不過考慮到基礎知識不一,如果對鎖的概念不太清晰的也可參考我這篇部落格https://blog.csdn.net/weixin_42504145/article/details/84995202   jvm---靜態方法加鎖和非靜態方法加鎖的區別 

ReentrantLock的可重入性

前言裡面提到,ReentrantLock重入性是基於Thread.currentThread()實現的: 如果當前執行緒已經獲得了鎖, 那該執行緒下的所有方法都可以獲得這個鎖。ReentrantLock的鎖依賴只有 NonfairSync和FairSync兩個實現類, 他們的鎖獲取方式大同小異。
 

 可重入性的實現基於下面程式碼片段的 else if 語句)(如果想要真正理解ReentrantLock的課重入性要仔細理解上面那片部落格,以及下面給出的else if 語句)

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();         // 獲取鎖的開始,首先讀volatile變數
    if (c == 0) {               // 這個if條件是沒有其他執行緒獲取該物件的鎖,我們用CAS嘗試獲取鎖
       if(ifFirst(current) && compareAndSetState(0,acquires)){
            setExclusiveOwnerThread(current);
            return(true)        // 這個方法在cas成功後,將物件的Mark Word裡的鎖標誌改成該執行緒
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        // 是當前執行緒,直接獲取到鎖。實現可重入性。
        int nextc = c + acquires;    // acquires預設傳入1,相當於讓nextc自增
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);             // 將標誌位改成自增後的值
        return true;
    }
    return false;
}

此處有兩個值需要關心:

  /**
     * The current owner of exclusive mode synchronization.
     * 持有該鎖的當前執行緒
     */
    private transient Thread exclusiveOwnerThread;

     -----------------兩個值不在同一個類----------------

    /**
     * The synchronization state.
     * 0: 初始狀態-無任何執行緒得到了鎖
     * > 0: 被執行緒持有, 具體值表示被當前執行緒持有的執行次數
     * 
     * 這個欄位在解鎖的時候也需要用到。
     * 注意這個欄位的修飾詞: volatile
     */
    private volatile int state;

 

公平鎖FairSync

公平鎖的實現機理在於每次有執行緒來搶佔鎖的時候,都會檢查一遍有沒有等待佇列,如果有, 當前執行緒會執行如下步驟:

if (!hasQueuedPredecessors() &&

compareAndSetState(0, acquires))

{ setExclusiveOwnerThread(current);

return true; }

其中hasQueuedPredecessors是用於檢查是否有等待佇列的。 

    public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

 非公平鎖NonfairSync

非公平鎖在實現的時候多次強調隨機搶佔: 

if (c == 0) {

if (compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true; }

與公平鎖的區別在於新晉獲取鎖的程序會有多次機會去搶佔鎖。如果被加入了等待佇列後則跟公平鎖沒有區別。 

ReentrantLock鎖的釋放

ReentrantLock鎖的釋放是逐級釋放的,也就是說在 可重入性 場景中,必須要等到場景內所有的加鎖的方法都釋放了鎖, 當前執行緒持有的鎖才會被釋放! 
釋放的方式很簡單, state欄位減一即可:

protected final boolean tryRelease(int releases) {
    //  releases = 1
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}