1. 程式人生 > >Java-併發-鎖-ReentrantLock

Java-併發-鎖-ReentrantLock

Java-併發-鎖-ReentrantLock

摘要

ReentrantLock是使用最廣的、最出名的AQS(AbstractQueuedSynchronizer)系列的可重入鎖。本文會分析他的lock, unlock等重要方法,還涉及公平/非公平概念對比,及對比synchronized。

關於Condition的分析,請參見本文姊妹篇:Java-併發-Condition

0x01 基本概念

ReentrantLock是使用最廣的、最出名的AQS(AbstractQueuedSynchronizer)系列的可重入鎖。它相對於LockSupport來說屬於是高層API,被鼓勵在使用者開發中使用。

ReentrantLock基本特點如下:

  • 等待可中斷
    獲取鎖時可以指定一個超時時間,如果超過這個時間還沒有拿到鎖就放棄等待
  • 公平性
    公平鎖就是按執行緒申請鎖時候FIFO的方式獲取鎖;而非公平鎖沒有這個規則,所有執行緒共同競爭,沒有先來後到一說
  • 繫結物件
    一個synchronized繫結一個Object用來wait, notify等操作;而ReentrantLock可以newCondition多次等到多個Condition例項,執行await, signal等方法。

0x02 實現原理

限於篇幅,這裡可以大概說下其原理。

2.1 AQS

AQS全稱AbstractQueuedSynchronizer

,他是ReentrantLock內部類NonfairSyncFairSync的父類Sync的父類,其核心元件如下:

  1. state,int 型別,用來儲存許可數
  2. Node雙向連結串列,儲存等待鎖的執行緒

Node就是AQS的內部類,這裡可以簡單看看Node定義:

static final class Node {
    // 表明等待的節點處於共享鎖模式,如Semaphore:addWaiter(Node.SHARED)
    static final Node SHARED = new Node();
    // 表明等待的節點處於排他鎖模式,如ReentranLock:addWaiter(Node.EXCLUSIVE)
static final Node EXCLUSIVE = null; // 執行緒已撤銷狀態 static final int CANCELLED = 1; // 後繼節點需要unpark static final int SIGNAL = -1; // 執行緒wait在condition上 static final int CONDITION = -2; // 使用在共享模式頭Node有可能處於這種狀態, 表示鎖的下一次獲取可以無條件傳播 static final int PROPAGATE = -3; // 這個waitStatus就是存放以上int狀態的變數,預設為0 // 用volatile修飾保證多執行緒時的可見性和順序性 volatile int waitStatus; // 指向前一個Node的指標 volatile Node prev; // 指向後一個Node的指標 volatile Node next; // 指向等待的執行緒 volatile Thread thread; // condition_queue中使用,指向下一個conditionNode的指標 Node nextWaiter; // 判斷是否共享鎖模式 final boolean isShared() { return nextWaiter == SHARED; } // 返回前驅結點,當前驅結點為null時丟擲NullPointerException final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } // 用來初始化wait佇列的構造方法;也被用來做共享鎖模式 Node() { } // 在addWaiter方法時,將指定Thread以指定模式放置 Node(Thread thread, Node mode) { this.nextWaiter = mode; this.thread = thread; } // Condition使用的構造方法 Node(Thread thread, int waitStatus) { this.waitStatus = waitStatus; this.thread = thread; } }

AQS的Node等待佇列雙向連結串列如下圖:
LockNodes

2.2 非公平鎖的實現

預設採用非公平的實現NonFairSync

2.2.1 lock()

lock()方法流程如下圖:

NonFairSync

可以看到,lock()方法最核心的部分就是可重入獲取許可(state),以及拿不到許可時放入一個AQS實現的雙向連結串列中,呼叫LockSupport.park(this)將自己阻塞。就算阻塞過程被中斷喚醒,還是需要去拿鎖,直到拿到為止,注意,此時在拿到鎖之後還會呼叫selfInterrupt()方法對自己發起中斷請求。

2.2.2 unlock()

unlock

2.3 公平鎖的實現

他的實現和非公平鎖有少許區別:

 static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
    // 這裡不再有非公平鎖的
    // if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());
    // 也就是說,公平鎖中,必須按規矩辦事,不能搶佔
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
        // 這裡多了一個 !hasQueuedPredecessors(),也就是不需要考慮wait連結串列
        // 否則就老實按流程走acquireQueued方法
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

下面看看的hasQueuedPredecessors實現

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    // h != t 代表wait連結串列不為空狀態
    // (s = h.next) == null代表wait連結串列已經初始化
    // s.thread != Thread.currentThread()代表當前執行緒不是第一個在wait連結串列排隊的執行緒
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

0x03 ReentrantLock和synchronized對比

ReentrantLock和synchronized對比如下:

可重入 等待可中斷 公平性 繫結物件數 效能優化
synchronized 支援 不支援 非公平 只能1個 較多
ReentrantLock 支援 支援 非公平/公平 可以多個 -

0x04 公平鎖與非公平鎖

非公平鎖比起公平鎖來說,唯一區別就是非公平鎖可以快速用compareAndSetState(0, acquires)進行搶佔,而公平鎖必須老老實實FIFO形式排隊;但unlock喚醒的時候是沒有區別的。

0x05 總結

  • state採用volatile,保證有序性和可見性
  • 大量使用如unsafe.compareAndSwapInt(this, stateOffset, expect, update);此類的CAS操作,保證原子性,同時在競爭小的時候效率勝過synchronized
  • 所謂的加鎖就是AQS.state++。且該鎖是可重入的,每次就state加1,unlock一次減一。兩個操作必須一一對應,否則其他等待鎖的執行緒永遠等待。
  • 所謂的等待鎖阻塞,就是放在一個連結串列裡,然後用LockSupport.park(this)阻塞
  • 就算用中斷喚醒已經等待鎖而阻塞的執行緒,依然必須直到獲取鎖才能執行。且在其後如果執行可中斷操作,會發生中斷!

關於Condition的分析,請參見本文姊妹篇:Java-併發-Condition