本文參考了https://blog.csdn.net/javazejian/article/details/75043422
ReentrantLock是在多線程問題處理中常用的鎖結構,作爲一種排它鎖 他比 synchronized要靈活,使用上卻要比 synchronized複雜一些。
要了解ReentrantLock的原理就不得不提到AQS,AQS 即 AbstractQueuedSynchronizer 的縮寫 翻譯爲抽象的同步隊列。提供了以Node爲基礎構建的雙向鏈表存儲請求鎖的線程。
我們來看以下ReentrantLock的類結構
我們這裏關注他的三個內部類,公平鎖 ,非公平鎖 和 Sync
可以看到Sync正是繼承自 AbstractQueuedSynchronizer,我們再來看一下AbstractQueuedSynchronizer
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/** Use serial ID even though all fields transient. */
private static final long serialVersionUID = 3737899427754241961L;
/**
* Empty constructor for use by subclasses.
*/
protected AbstractOwnableSynchronizer() { }
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
/**
* Sets the thread that currently owns exclusive access.
* A {@code null} argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* {@code volatile} field accesses.
* @param thread the owner thread
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
/**
* Returns the thread last set by {@code setExclusiveOwnerThread},
* or {@code null} if never set. This method does not otherwise
* impose any synchronization or {@code volatile} field accesses.
* @return the owner thread
*/
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
這個類就很簡潔了,他定義了當前持有鎖的線程。我們再回頭來看AbstractQueuedSynchronizer。
我們來看一下Node的定義
static final class Node {
/** 標記爲共享鎖*/
static final Node SHARED = new Node();
/** 標記爲排他鎖*/
static final Node EXCLUSIVE = null;
//以下4個狀態都是 waitStatus 所支持的狀態 標識當前Node 中線程的狀態
/** 當前線程可以取消 */
static final int CANCELLED = 1;
/** 當前線程可以被喚醒 */
static final int SIGNAL = -1;
/** 當前線程等待condition 條件 */
static final int CONDITION = -2;
/**
* 在共享模式中使用表示獲得的同步狀態會被傳播
*/
static final int PROPAGATE = -3;
/** 等待狀態 */
volatile int waitStatus;
/** 上一個節點*/
volatile Node prev;
/** 下一個節點*/
volatile Node next;
/** 當前節點持有的線程*/
volatile Thread thread;
/**
* 等待隊列中的後繼結點,這個與Condition有關
*/
Node nextWaiter;
/**
* 是否共享鎖
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 獲取上一個節點
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
看完了Node 我們來看一下AbstractQueuedSynchronizer 自身的屬性
/**
* 隊首
*/
private transient volatile Node head;
/**
* 隊尾
*/
private transient volatile Node tail;
/**
* 鎖狀態 爲 0 時 沒有線程持有鎖
*/
private volatile int state;
在AbstractQueuedSynchronizer 中提供了很多方法維護Node的數據結構,以及定義了獲取鎖和釋放鎖的基礎方法。瞭解了他們的內部結構,我們來看這張圖
這張圖描述了AQS的相關類的關係。
AbstractOwnableSynchronizer:抽象類,定義了存儲獨佔當前鎖的線程和獲取的方法
AbstractQueuedSynchronizer:抽象類,AQS框架核心類,其內部以虛擬隊列的方式管理線程的鎖獲取與鎖釋放,其中獲取鎖(tryAcquire方法)和釋放鎖(tryRelease方法)並沒有提供默認實現,需要子類重寫這兩個方法實現具體邏輯,目的是使開發人員可以自由定義獲取鎖以及釋放鎖的方式。
Node:AbstractQueuedSynchronizer 的內部類,用於構建虛擬隊列(鏈表雙向鏈表),管理需要獲取鎖的線程。
Sync:抽象類,是ReentrantLock的內部類,繼承自AbstractQueuedSynchronizer,實現了釋放鎖的操作(tryRelease()方法),並提供了lock抽象方法,由其子類實現。
NonfairSync:是ReentrantLock的內部類,繼承自Sync,非公平鎖的實現類。
FairSync:是ReentrantLock的內部類,繼承自Sync,公平鎖的實現類。
ReentrantLock:實現了Lock接口的,其內部類有Sync、NonfairSync、FairSync,在創建時可以根據fair參數決定創建NonfairSync(默認非公平鎖)還是FairSync。
接下來我們從一個實例出發,從源碼上看一個線程從獲取鎖到釋放鎖的過程。
/**
* @Description
* @Author changyandong
* @Emoji (゜ - ゜)つ乾杯
* @Created Date: 2020/4/1 9:12
* @ClassName TestThread
* @Version: 1.0
*/
public class TestThread implements Runnable {
static ReentrantLock reentrantLock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
reentrantLock.lock();
try {
IntStream.range(0,10000).forEach(k->i++);
}finally {
reentrantLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
TestThread testThread = new TestThread();
Thread thread1 = new Thread(testThread);
Thread thread2 = new Thread(testThread);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(TestThread.i);
}
}
我啓動兩個線程操作共享資源i 我們先來分析lock()
public void lock() {
sync.lock();
}
// 默認情況下我們使用的是非公平鎖實現 NonfairSync
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
可以看到,他嘗試使用cas將state從0設置爲1,如果設置成功,當前線程就是持有鎖的線程否則acquire(1);
acquire是AbstractQueuedSynchronizer中的方法定義如何獲取鎖和進入等待隊列
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
我們這裏先看看tryAcquire
// 非公平鎖的嘗試獲取
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) {
// cas 如果當前沒有人持有鎖 設置state 爲1
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果是重入 將state + 1
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;
}
如果這裏返回false表示嘗試獲取鎖失敗,那麼就應該將當前線程置入隊列中也就是acquireQueued的邏輯,但是在他之前我們看一下addWaiter()
addWaiter() 也是 AbstractQueuedSynchronizer定義的創建node並加入隊尾的方法
private Node addWaiter(Node mode) {
// 將當前線程封裝爲node
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的上一個節點爲 之前的隊尾
node.prev = pred;
// cas 嘗試將 tail設置爲node
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// cas設置失敗
enq(node);
return node;
}
private Node enq(final Node node) {
// 無限循環的for
for (;;) {
// 獲取隊尾
Node t = tail;
// 當前隊列還沒初始化
if (t == null) { // Must initialize
// 設置隊首,並將隊首設爲隊尾
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 否則一直嘗試cas設置 隊尾 直到 成功
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
接着來看 添加隊列方法acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 無限循環
for (;;) {
// 獲取 node 的前置節點
final Node p = node.predecessor();
// 如果前置爲 head 那麼就嘗試獲取鎖
if (p == head && tryAcquire(arg)) {
// 獲取鎖成功,當前node就變成了 head 並釋放原head
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果p不是head 或者獲取鎖失敗,那麼判斷是否需要Park線程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 如果出了問題 比如 shouldParkAfterFailedAcquire的空指針 那麼放棄 node
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 獲取上一個節點的等待狀態
int ws = pred.waitStatus;
// 如果是喚醒狀態 返回true
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
// 如果狀態大於 0 說明線程應該被結束 釋放節點
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
// ws = 0 或者 -3 將它cas爲 -1
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 鎖住當前線程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
到這裏 lock方法的流程就結束了。接下來看unLock() 這個就相對簡單了
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 如果鎖已經沒有線程持有了 就喚醒一個隊列裏的Node
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//tryRelease 很簡單 就是改state 如果 state爲0 將持有線程設置爲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;
}
private void unparkSuccessor(Node node) {
/*
* 設置waitStatus爲 0
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* 判斷下一個節點是否應該被喚醒,如果不是 倒序找到一個需要被喚醒的節點 喚醒 他
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 這裏循環沒有退出 找到的是node節點的後續節點中最早進入隊列的一個 所有unpark後 他的prev 就是head
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 過程
接下來我們來看公平鎖,公平鎖保證嚴格的FIFO,不會再acquire之前先嚐試設置state,公平鎖與非公平鎖的區別就在tryAcquire()方法的實現略有區別。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 如果c == 0 hasQueuedPredecessors 判斷的是 隊列是否爲 空或者 當前線程是重入才執行cas操作
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;
}
}
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.
// aqs 會維護head 賦值一定在tail 之前,這樣我們獲取時就要先獲取tail 保證線程安全
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
// h== t 隊列爲空 h.next == null 隊列爲空 s.thread == Thread.currentThread() 重入 因爲下一個獲取鎖的線程爲本線程所以符合公平鎖定義
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
下面放上我自己畫的 lock流程
關於Condition 之後再補充