可重入鎖原始碼分析
ReentrantLocks意為可重入鎖,也就是獲得鎖的同一個執行緒可以多次獲得鎖而不會阻塞。但是同一時間內鎖只能被同一個執行緒持有,同時ReentrantLock又分為公平鎖和非公平鎖,但是他們都是通過維護一個節點佇列來實現,只不過公平鎖每次都取頭結點執行,而非公平鎖每次可能隨機取節點執行。無論那種情況,每當一個執行緒獲得鎖之後都會使狀態加一,當狀態為0時會通知後面排隊的節點(或者是在非公平鎖中某一個正好合適的執行緒執行)。
/**
* 一個抽象類,是鎖同步控制的基礎。子類有公平鎖和非公平鎖兩種。
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* 預設是非公平鎖.
*/
abstract void lock();
/**
* 非公平鎖嘗試獲取資源.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {//狀態為0,表示還沒有執行緒獲得鎖
if (compareAndSetState(0, acquires)) {//CAS方式設定資源
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;//沒獲得資源,返回false
}
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;//釋放成功返回true
}
protected final boolean isHeldExclusively() {//當前執行緒是否是獲得了排他屬性的執行緒
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {//當前執行緒鎖的次數
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {//判斷是否有鎖
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
Syn的兩種實現方式:公平鎖和非公平鎖。公平鎖的語義和非公平鎖的語義不同在於,公平鎖每次從佇列的首部獲取等待最久的執行緒,而非公平鎖則每次隨機獲取執行緒執行,很可能某一個執行緒多次獲取鎖,導致其他執行緒飢餓。在下面程式碼中可以看出,非公平鎖首先使用CAS設定狀態,也就是說如果鎖是空就直接佔有,然後進入acquire進行處理。而公平鎖則直接進入acquire(1).
/**
* 非公平鎖
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))//CAS設定當前為0 的時候上鎖
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//否則嘗試獲得鎖。
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* 公平鎖
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
*
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {//沒有前驅節點並且CAS設定成功
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;
}
}
acquire中的詳細介紹。acquire是AQS類中的方法。其中tryAcquire就是(非)公平鎖類下的tryAcquire方法,若沒有獲得資源那麼就繼續執行,否則結束此函式。addWaiter用於新增節點,也就是把當前執行緒對應的節點插入CLH佇列的尾部。
/**
* 這個方法也就是lock()方法的關鍵方法。tryAcquire獲得資源,返回true,直接結束。若未獲取資源,新建一個節點插入隊尾,
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//獲取資源立刻結束
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//沒有被中斷過,也結束
selfInterrupt();
}
/**
* 為當前執行緒和模式建立一個節點,這個也是AQS中的類。
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
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;//設定pred為尾節點這裡嘗試一次。
if (pred != null) {//尾節點不為空則設定新節點前驅為尾節點
node.prev = pred;
if (compareAndSetTail(pred, node)) {//CAS原子操作將node設定為新的尾節點
pred.next = node;
return node;//pred下一個節點為新的尾節點。並返回新的尾節點
}
}
enq(node);//上面沒成功,將節點插入隊尾
return node;
}
/**
*把一個節點插入隊尾中
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;//將尾節點賦值給t
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))//尾節點為空CAS設定新節點為頭結點
tail = head;
} else {
node.prev = t;//node 前置節點設定為t
if (compareAndSetTail(t, node)) {//CAS操作設定node 為尾節點
t.next = node;
return t;//返回新的尾節點。
}
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;//表示當前執行緒在休眠過程中有沒有被中斷過
for (;;) {//迴圈獲取前驅節點,一直到前驅是頭結點,並且某個節點獲取了資源
final Node p = node.predecessor();//獲得node節點的前驅節點
if (p == head && tryAcquire(arg)) {//p節點為頭結點,當前執行緒獲得了資源
setHead(node);//當前節點設定為頭結點,取消排隊
p.next = null; // 把node 設定為null
failed = false;
return interrupted;//返回是否中斷過
}
if (shouldParkAfterFailedAcquire(p, node) &&//判斷當前執行緒是否應該阻塞
parkAndCheckInterrupt())//阻塞當期執行緒,在這裡執行park操作
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* 檢查自己在沒有獲得資源之後,是不是應該掛起。
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//獲得前置節點的狀態
if (ws == Node.SIGNAL)
/*
* 如果前置節點的狀態是sigal,那麼就可以返回true,也就是意味著執行緒可以被阻塞了。
*/
return true;
if (ws > 0) {
/*
* 如果前置節點的狀態是刪除狀態,那麼就一直找到一個正常的狀態排在它後面。
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
*如果前驅正常,就把前驅狀態設定為sigal
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
這裡簡單總結一下lock的過程:
首先嚐試獲取資源,如果當前狀態為0,表示沒有執行緒佔有鎖,設定該執行緒為獨佔模式,使用CAS設定狀態,否則如果當前執行緒和獨佔執行緒是一個執行緒,修改狀態值,否則返回false。
若獲取資源失敗,則通過addWaiter方法建立一個節點並放在CLH佇列的尾部。
逐步去執行CLH佇列中的執行緒,當前執行緒會公平性的阻塞一直到獲取鎖為止,返回執行緒在等待的過程中還是否中斷過。
unLock方法比較簡單,直接release(1)即可 。
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {//嘗試釋放鎖,其實就是狀態減一。
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//成功則喚醒後繼節點
return true;
}
return false;
}
// 執行緒已被取消
static final int CANCELLED = 1;
// 當前執行緒的後繼執行緒需要被unpark(喚醒)
// 一般發生情況是:當前執行緒的後繼執行緒處於阻塞狀態,而當前執行緒被release或cancel掉,因此需要喚醒當前執行緒的後繼執行緒。
static final int SIGNAL = -1;
// 在Condition休眠狀態,在等待Condition喚醒
static final int CONDITION = -2;
// (共享鎖)其它執行緒獲取到“共享鎖”,對應的waitStatus的值
static final int PROPAGATE = -3;
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();//持鎖執行緒和當前執行緒不一致丟擲異常
boolean free = false;
if (c == 0) {//狀態為0 說明鎖已經不被執行緒所佔有
free = true;
setExclusiveOwnerThread(null);//設定佔有鎖執行緒為null
}
setState(c);//設定鎖的狀態
return free;
}
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)//獲取當前節點狀態,若小於0則置狀態為0
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
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);//喚醒之
}
一次unlock操作需要修改狀態位,然後喚醒節點。整個釋放操作也是使用unpark()來喚醒佇列最前面的節點。其實lock中比較重要的也就是lock和release,它們又和AQS聯絡緊密,下面會單獨談談AQS的重要方法。參考資料:
http://ifeve.com/juc-aqs-reentrantlock/
http://ifeve.com/java-special-troops-aqs/
http://www.cnblogs.com/skywang12345/p/3496147.html#p24
https://www.cnblogs.com/waterystone/p/4920797.html
相關文章
- ReentrantLock可重入鎖——原始碼詳解ReentrantLock原始碼
- Redisson 分散式鎖原始碼 01:可重入鎖加鎖Redis分散式原始碼
- ZooKeeper 分散式鎖 Curator 原始碼 01:可重入鎖分散式原始碼
- ZooKeeper 分散式鎖 Curator 原始碼 03:可重入鎖併發加鎖分散式原始碼
- 可重入鎖與不可重入鎖理解
- 可重入鎖
- Springboot基於Redisson實現Redis分散式可重入鎖【案例到原始碼分析】Spring BootRedis分散式原始碼
- 死鎖和可重入鎖
- ZooKeeper 分散式鎖 Curator 原始碼 02:可重入鎖重複加鎖和鎖釋放分散式原始碼
- redis分散式鎖-可重入鎖Redis分散式
- Golang可重入鎖的實現Golang
- Java併發包原始碼學習系列:ReentrantLock可重入獨佔鎖詳解Java原始碼ReentrantLock
- Java 重入鎖 ReentrantLock 原理分析JavaReentrantLock
- 從原始碼入手詳解ReentrantLock,一個比synchronized更強大的可重入鎖原始碼ReentrantLocksynchronized
- Java併發-顯式鎖篇【可重入鎖+讀寫鎖】Java
- Redis實現可重入的分散式鎖Redis分散式
- Go 為什麼不支援可重入鎖?Go
- java中如何實現可重入的自旋鎖Java
- ReentrantLock可重入鎖、公平鎖非公平鎖區別與實現原理ReentrantLock
- Java 種15種鎖的介紹:公平鎖,可重入鎖,獨享鎖,互斥鎖等等Java
- Java 種15種鎖的介紹:公平鎖,可重入鎖,獨享鎖,互斥鎖等等...Java
- 【Java面試】什麼是可重入,什麼是可重入鎖? 它用來解決什麼問題?Java面試
- 重入鎖的理解
- 【java併發程式設計】ReentrantLock 可重入讀寫鎖Java程式設計ReentrantLock
- 【Java】深入理解ReentrantLock可重入鎖之簡單使用JavaReentrantLock
- Lock鎖之重入鎖與讀寫鎖
- java重入鎖、公平鎖和非公平鎖Java
- synchronized鎖重入問題synchronized
- Java 中15種鎖的介紹:公平鎖,可重入鎖,獨享鎖,互斥鎖,樂觀鎖,分段鎖,自旋鎖等等Java
- Go 互斥鎖 Mutex 原始碼分析(二)GoMutex原始碼
- 原始碼分析:ReentrantReadWriteLock之讀寫鎖原始碼
- Java 讀寫鎖 ReentrantReadWriteLock 原始碼分析Java原始碼
- Java併發基礎-鎖的使用及原理(可重入鎖、讀寫鎖、內建鎖、訊號量等)Java
- Lock介面、重入鎖ReentrantLock、讀寫鎖ReentrantReadWriteLockReentrantLock
- golang RWMutex讀寫互斥鎖原始碼分析GolangMutex原始碼
- React - setState原始碼分析(小白可讀)React原始碼
- 故障分析 | 從 Insert 併發死鎖分析 Insert 加鎖原始碼邏輯原始碼
- AQS學習(二) AQS互斥模式與ReenterLock可重入鎖原理解析AQS模式