1、構造器
ReentrantLock可以構造兩種鎖:公平鎖和非公平鎖
我們可以先複習一下概念
1.1、非公平鎖
每個等待中的線程都有相等的機率搶佔到鎖(不排隊,可以插隊,所以不公平)
優點:吞吐量高,因爲在鎖被其他資源搶佔時,公平鎖需要將每個新來的線程依次掛起放入等待隊列,而非公平鎖有機率使新來的線程直接獲取到鎖,性能的差異就在於新線程的掛起和喚醒
缺點:在等待隊列中的線程有可能長時間獲取不到鎖,處於飢餓狀態
默認構建非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
1.2、公平鎖
先進入等待隊列的線程先獲取鎖
優缺點與非公平鎖相反,吞吐量差點,但是線程不會出現飢餓
傳參構造非公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
至於兩者具體的實現差別在哪,往下看就知道了
2、非公平鎖實現邏輯
我們以最簡單的demo開始:
主線程獲取鎖對象,然後創建一個子線程再嘗試獲取鎖對象,如果主線程不解鎖,那子線程就會一直處於park狀態
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
Thread.sleep(1000);
new Thread(() -> reentrantLock.lock()).start();
}
2.1、非公平鎖構造器
NonfairSync這個類繼承了Sync,Sync又繼承了AbstractQueuedSynchronizer
AQS這個類很著名也很重要,裏面有很多成員變量和方法與CLH隊列的實現有關,所以可以認爲一個鎖對象對應一個AQS,在下面的實現邏輯中會串進去講
2.2、非公平鎖獲取鎖
可以看到,線程在第一次獲取非公平鎖的時候,什麼也不顧及直接先嚐試CAS獲取鎖,對比公平鎖會直接調用acquire,後面講
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
2.2.1、compareAndSetState——獲取鎖
加鎖的時候,會先進到compareAndSetState方法中,這個方法就是給鎖對象加鎖,原理是嘗試使用CAS改變鎖對象的狀態,方法在AQS中實現,是unsafe中對應方法的封裝。unsafe裏都是native方法,跟java的底層c庫有關,就先不拓展講了(主要我也沒看過)
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
這個stateOffset是個靜態變量,放在static代碼塊中,在new第一個鎖的時候就已經初始化,可以理解爲存儲state字段的內存地址與AQS起始地址的偏移量。同時還初始化了其他偏移量,都是底層用來尋址的。這些靜態變量並不影響AQS對象的內部狀態。
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
下面是AQS的前3個成員變量,可以看見state之前有兩個對象,64位JVM虛擬機用8字節存儲一個對象引用,所以你debug的時候可以看到stateOffset初始化之後值就是16
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
2.2.2、setExclusiveOwnerThread——設置擁有鎖的線程
加鎖成功會執行setExclusiveOwnerThread,把有鎖的線程緩存起來
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
2.2.3、acquire——嘗試重入鎖和排隊
2.2.3.1、重入鎖
可重入鎖的概念就是,同一個線程可以在不釋放鎖的情況下,多次獲取同一把鎖,下面來看邏輯
加鎖失敗後會進入acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
進入tryAcquire方法,它是nonfairTryAcquire的封裝
首先它會再嘗試獲取一次鎖(之前CAS已經失敗了,現在又試一次,兩次獲取鎖之間的代碼只有Thread.currentThread(),不知道是不是爲了極限性能,提高非公平性),成功了就返回“獲取成功”
失敗了就判斷持有鎖的線程是不是自己,是自己就給state加1,表示加鎖的次數,這就是可重入鎖的實現邏輯
如果擁有鎖的線程都不是自己的,就返回“獲取失敗”
對比公平鎖的實現,少了一步hasQueuedPredecessors判斷隊列中是否有等待的線程,公平鎖的具體實現我們也後面再講
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
當這次嘗試獲取鎖成功了,方法就結束了,獲取失敗了會怎麼樣呢
2.2.3.2、排隊
會進入acquireQueued(addWaiter(Node.EXCLUSIVE), arg)這個方法
首先來看addWaiter方法,他的作用是向隊列中加入排隊線程的節點
如果隊列裏有尾節點,就把該線程的節點與尾節點關聯上然後返回
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
反之進入enq方法,這個方法是用來初始化隊列首尾節點的,跟LinkedList結構有點像
其中compareAndSetHead和compareAndSetTail也都是調用unsafe方法,傳AQS對象和對應參數的偏移量,套路相同
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
從addWaiter出來之後,返回了當前加入隊列的節點,就是想要獲取鎖的該線程所對應的節點,當作參數進入acquireQueued方法中
acquireQueued這個方法是真正讓線程進入阻塞狀態的方法
在進入for循環後,會進入shouldParkAfterFailedAcquire方法檢查前一個節點的waitStatus並置爲-1,表示該節點(其實原意應該是前一個節點後面的節點,意思都一樣)應該park,然後在第二次循環時執行parkAndCheckInterrupt
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
parkAndCheckInterrupt是LockSupport.park的封裝
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
線程停就停在了UNSAFE.park這個方法,等上一個獲取鎖的線程釋放鎖,這個線程就能從park中解脫出來,繼續往下走
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
線程從park中解脫之後,會回到acquireQueued方法,在下一次for循環中進入(p == head && tryAcquire(arg))這個條件,嘗試獲取鎖,獲取到了就返回,沒獲取到就繼續之前幾步。其中會判斷線程的interrupted中斷狀態,進行自我中斷
(這裏也補充一點,因爲在線程進入隊列之後就不會被中斷,外部調用該線程的中斷方法,該線程也不會立刻響應,只會給該線程做一個“需要中斷”的標記並喚醒該線程,並讓該線程從UNSAFE.park中解脫出來,然後再次進入acquireQueued的for循環中,等該線程獲取到鎖之後再自己調用中斷)
2.2.3、非公平鎖釋放鎖
釋放鎖調用unlock方法,是release方法的封裝
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
首先調用tryRelease方法釋放鎖
首先該線程肯定得是鎖的擁有者,然後鎖狀態-1並更新,如果-1之後鎖狀態爲0說沒有重入鎖了,就把線程擁有者置空
protected final boolean tryRelease(int releases) {
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;
}
回到release方法,如果頭節點的waitStatus不是0,說明還有線程在park狀態,需要被喚醒,執行unparkSuccessor方法
可以看到,unparkSuccessor傳進來的參數是隊列的虛擬頭節點,所以進入隊列的線程對鎖的獲取都是公平的,都是順序喚醒的
當虛擬頭節點的下一個節點無效的時候,就無法再順序找到下一個有效的節點,所以會從尾節點往前找,然後找到第一個有效的節點
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
釋放鎖或喚醒之後,各個線程就會繼續執行他們後面的邏輯,非公平鎖的部分就告一段落了
3、公平鎖實現邏輯
那我們主要說與非公平鎖的實現差異
3.1、構造器
傳true構造公平鎖
ReentrantLock reentrantLock = new ReentrantLock(true);
3.2、獲取鎖
直接調用acquire方法,而非公平鎖會先嚐試CAS獲取一次鎖
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire的實現不同
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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,用來檢查隊列中是否有park線程,保證先來的線程先獲取鎖,實現線程獲取鎖的公平性
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());
}
其餘地方與非公平鎖調用的都是相同的方法
以上就是ReetrantLock源碼的解讀,講的不清楚的或者有問題的可以留言,互相討論互相進步,同時歡迎點贊