1.ReentrantLock加鎖原理
1.第一個線程進來
FairSync裏的lock方法
final void lock() {
// 加鎖成功後,修改的值
acquire(1);
}
其抽象父類的方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
判中的第一個tryAcquire方法,
其父類方法時這樣的,但是我們要看的時fairSync(公平鎖的)
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected final boolean tryAcquire(int acquires) {
// 拿到當前線程
final Thread current = Thread.currentThread();
// 拿到鎖的狀態;C = 0 就是沒有佔用鎖
int c = getState();
// 沒有人佔用,
if (c == 0) {
// hasQueuedPredecessors 是否有線程等待
// 當hasQueuedPredecessors返false,compareAndSetState就加鎖
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;
}
}
aqs的屬性
// 頭節點
private transient volatile Node head;
// 尾節點
private transient volatile Node tail;
// 線程的狀態(計數)
private volatile int state;
aqs的隊列裏,頭節點header線程對象一直是null,所有等待線程都是排在後面的
public final boolean hasQueuedPredecessors() {
// 隊尾
Node t = tail; // Read fields in reverse initialization order
// 隊頭
Node h = head;
Node s;
// 第一個線程來時,h!=t 返回的時false,隊列還沒初始化,兩個都是null
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
再返回acquire方法
public final void acquire(int arg) {
// 如果第一個線程拿到鎖,返回true,那麼這裏第一個判斷就返回false,就結束執行
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
2.第二個線程來的時候,再次走tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 拿到鎖的狀態;C = 1 就是沒有佔用鎖
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 當C不等於0 時,判斷當前線程是否是是加鎖的那個線程,如果這個判斷進去就表示,重入鎖
else if (current == getExclusiveOwnerThread()) {
// 這裏acquire = 1,nextc = 2
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 這裏設置state = 2,表示該線程進入2次,然後返回true
setState(nextc);
return true;
}
// 當前面兩個判斷走完,基本就確定該線程是拿不到鎖的
return false;
}
}
返回後,進入acquireQueued方法
public final void acquire(int arg) {
// 如果第一個線程拿到鎖,返回true,那麼這裏第一個判斷就返回false,就結束執行
if (!tryAcquire(arg) &&
// 如果第二個線程在上一個操作中沒有獲取到鎖
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
private Node addWaiter(Node mode) {
// 實例化當前線程節點
Node node = new Node(Thread.currentThread(), mode);
// 這裏tail爲空,因爲還沒初始化
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 進入這個方法;進行類似添加的操作,將node添加到隊列中
enq(node);
return node;
}
只有一個線程來的時候,並沒有走隊列的邏輯,只是判斷了隊列是否由隊頭隊尾,然後就設置了加鎖狀態了,所有當第二個線程來的時候,aqs隊列還是沒有初始化的。
private Node enq(final Node node) {
for (;;) {
// 將tail(null)給t
Node t = tail;
// 第一次,
if (t == null) { // Must initialize
// new 一個node給隊頭
if (compareAndSetHead(new Node()))
// 再把隊頭給tail(隊尾)
tail = head;
} else {
// 再次循環進入,將t(現在已經有實例化後的了)設置爲自己的前一個節點
node.prev = t;
// 將自己加到隊尾,設置前一個節點的下一個節點爲自己
if (compareAndSetTail(t, node)) {
t.next = node;
// 返回當前節點的上一個節點
return t;
}
}
}
}
這時,可以看到,第一次for,它new 了一個隊頭,空線程對象,並且tail = head,第二次for,t = tail = head
,compareAndSetTail設置當前節點爲隊尾,t.next 執行當前線程節點,return 跳出循環。
爲什麼頭節點的線程對象要設置爲空的??
第一個線程來的時候,拿到鎖,當前線程對象已經有了,aqs裏的隊列裏不需要再保存一次,而且,隊列裏的對拿到鎖的線程對象不做任何操作,無意義。
然後結束返回:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
// addWaiter將當前節點加入到隊尾
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
通過判斷當前節點是不是在隊頭來判斷是不是被park
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;
// 返回false
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
這裏的node是上一個節點,第二個線程進來,那麼node就是頭節點,它會判斷是否是頭節點,然後tryAcquire嘗試加鎖。
第一個判斷中,加鎖失敗後,進入第二個判斷,然後進入這個方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// Node.SIGNAL = -1,初始化後的這個狀態是0,表示等待
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 進入這個方法,將上一個節點的watiStatus賦值爲 -1,表示上一個節點時活躍狀態,且下一個線程節點需要運行
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
waitStatus包含的狀態有:
-
CANCELLED,值爲1,表示當前的線程被取消;
-
SIGNAL,值爲-1,表示當前節點的後繼節點包含的線程需要運行,也就是unpark;
-
CONDITION,值爲-2,表示當前節點在等待condition,也就是在condition隊列中;
-
PROPAGATE,值爲-3,表示當前場景下後續的acquireShared能夠得以執行;
-
值爲0,表示當前節點在sync隊列中,等待着獲取鎖。
走完這個方法後,因爲循環,又會走到shouldParkAfterFailedAcquire
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;
}
// 第一次進入後修改值後,再循環後又會進到這裏,這時shouldParkAfterFailedAcquire返回爲true,
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
第二次循環shouldParkAfterFailedAcquire方法返回true後,執行parkAndCheckInterrupt方法進行park,嘗試獲取鎖,然後獲取不到鎖,如果獲取不到就阻塞,返回中斷標記並重置狀態
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
如果park拿到鎖了,就加鎖成功,那麼interrupted就是false,就不會進入selfInterrupt方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
然後上面的步驟再來一遍進入shouldParkAfterFailedAcquire
而非公平鎖的lock,再加鎖失敗後就進入排隊,就和上面的公平鎖是一樣的流程。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 如果加鎖失敗,就和公平鎖的排隊一樣
acquire(1);
}
大概畫了一個AQS結構:
2.ReentrantLock解鎖過程
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;
}
protected final boolean tryRelease(int releases) {
// 如果state = 1(線程狀態非重入鎖),c = 0
int c = getState() - releases;
// 這裏判斷當前線程是否是加鎖的線程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 修改狀態
free = true;
// 修改加鎖的線程爲null
setExclusiveOwnerThread(null);
}
// 修改狀態爲 c = 0
setState(c);
// 返回true
return free;
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
// 判斷頭節點是否爲null,且該線程節點是否是活躍狀態,如果只有一個線程時,沒有等待隊列,head = null,h = null,
// 如果時多於1個線程時,h != null,判斷waitStatus狀態,是否時隊頭,因爲在添加等待隊列的時候,會把上一個節點的waitStatus改爲-1,那麼waitStatus = 0 就是表示時隊尾
if (h != null && h.waitStatus != 0)
// 釋放
unparkSuccessor(h);
// 返回true
return true;
}
return false;
}
進入unparkSuccessor這個方法表示,有多個線程在排隊,且該線程不在隊尾,時隊頭waitStatus = -1
private void unparkSuccessor(Node node) {
// 頭結點的狀態
int ws = node.waitStatus;
if (ws < 0)
// 修改對頭節點的waitStaus = 0
compareAndSetWaitStatus(node, ws, 0);
// 獲取下一個節點
Node s = node.next;
// 下一個節點不可能爲空,因爲頭節點爲-1,是因爲它的下一個節點修改的,waitStaus > 0 表示線程被取消,也不可能,所以不會進入
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);
}
假設:s == null || s.waitStatus > 0成立,那麼這個對立的節點可能存在人爲的操作致使隊列發生變化,
if (s == null || s.waitStatus > 0) {
// 設置該節點爲null
s = null;
// 條件從尾節點開始,只要不等於空,且不等於當傳進來的節點也就是異常節點,就往前遍歷
for (Node t = tail; t != null && t != node; t = t.prev)
// 找到等待狀態 = -1的,也就是持有鎖定節點
if (t.waitStatus <= 0)
s = t;
}
調用unsafe的unpark方法
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
這個方法執行後就會在執行park方法的地方繼續執行,即第一個線程持有鎖是,第二個線程也來排隊,
private final boolean parkAndCheckInterrupt() {
// 在這裏被阻塞了,
LockSupport.park(this);
return Thread.interrupted();
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
// 那麼當調用unpark方法後,會從這裏開始執行。
setBlocker(t, null);
}