之前的文章中簡單的為大家介紹了重入鎖JAVA并發之多線程基礎(2)。這里面也是簡單的為大家介紹了重入鎖的幾種性質,這里我們就去探索下里面是如何實現的。
我們知道在使用的時候,必須鎖先有定義,然后我們再拿著當前的鎖進行加鎖操作,然后處理業務,最后是釋放鎖的操作(這里就拿里面非公平鎖的實現來講解)。
字節碼操作
public class com.montos.lock.ReentrantLockDemo implements java.lang.Runnable {
public static java.util.concurrent.locks.ReentrantLock lock;
public static int k;
public com.montos.lock.ReentrantLockDemo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public void run();
Code:
0: iconst_0
1: istore_1
2: iload_1
3: sipush 1000
6: if_icmpge 29 //int類型的值進行棧頂比較
9: getstatic #2 // Field lock:Ljava/util/concurrent/locks/ReentrantLock;
12: invokevirtual #3 // Method java/util/concurrent/locks/ReentrantLock.lock:()V
15: getstatic #4 // Field k:I
18: iconst_1
19: iadd
20: putstatic #4 // Field k:I
23: iinc 1, 1
26: goto 2
29: iconst_0
30: istore_1
31: iload_1
32: sipush 1000
35: if_icmpge 50
38: getstatic #2 // Field lock:Ljava/util/concurrent/locks/ReentrantLock;
41: invokevirtual #5 // Method java/util/concurrent/locks/ReentrantLock.unlock:()V
44: iinc 1, 1
47: goto 31
50: return
public static void main(java.lang.String[]) throws java.lang.InterruptedException;
Code:
0: new #6 // class com/montos/lock/ReentrantLockDemo
3: dup
4: invokespecial #7 // Method "":()V
7: astore_1
8: new #8 // class java/lang/Thread
11: dup
12: aload_1
13: invokespecial #9 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V
16: astore_2
17: new #8 // class java/lang/Thread
20: dup
21: aload_1
22: invokespecial #9 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V
25: astore_3
26: aload_2
27: invokevirtual #10 // Method java/lang/Thread.start:()V
30: aload_3
31: invokevirtual #10 // Method java/lang/Thread.start:()V
34: aload_2
35: invokevirtual #11 // Method java/lang/Thread.join:()V
38: aload_3
39: invokevirtual #11 // Method java/lang/Thread.join:()V
42: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
45: getstatic #4 // Field k:I
48: invokevirtual #13 // Method java/io/PrintStream.println:(I)V
51: return
static {};
Code:
0: new #14 // class java/util/concurrent/locks/ReentrantLock
3: dup
4: invokespecial #15 // Method java/util/concurrent/locks/ReentrantLock."":()V
7: putstatic #2 // Field lock:Ljava/util/concurrent/locks/ReentrantLock;
10: iconst_0
11: putstatic #4 // Field k:I
14: return
}
復制代碼
這里面無非就是入棧,棧元素比較,出棧放入變量中這些操作,沒有之前的synchronized里面的監視器相關指令限制,只是簡單的一些棧操作。
加鎖操作
final void lock(){
if (compareAndSetState(0, 1)) //將同步狀態從0變成1 采用cas進行更新
setExclusiveOwnerThread(Thread.currentThread());//設置當前擁有獨占訪問權的線程。
else
acquire(1);//沒有獲取到鎖,則進行嘗試操作
}
復制代碼
往下面的選擇走:
public final void acquire(int arg){
//先進行再次嘗試獲取鎖的操作,如果獲取失敗則將當前加入隊列中,并設置中斷標志。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
復制代碼
首先走嘗試獲取鎖的操作(這里還是走非公平鎖的):
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;
}
復制代碼
接著往下走:
private Node addWaiter(Node mode){
//獨占模式進行封裝當前線程
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {如果尾節點不為null,將當前的節點接入并返回
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
復制代碼
繼續往下走:
private Node enq(final Node node){
for (;;) {//
Node t = tail;
if (t == null) { // 初始化尾節點
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, 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();
if (p == head && tryAcquire(arg)) {//如果當前節點前一個節點是頭節點,并嘗試獲鎖成功
setHead(node);//設置當前的頭結點
p.next = null; // 手動清除引用 幫助GC
failed = false;
return interrupted;
}
//檢測獲取鎖失敗的節點狀態 以及暫時掛起并返回當前的中斷標志
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);//取消正在進行的獲取嘗試。
}
}
復制代碼
說真的,咱們直接看失敗的情況,我們接著往下走:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node){
//檢查和更新無法獲取的節點的狀態。
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//該節點已經設置了請求釋放信號狀態,所以可以進行安全掛起
return true;
if (ws > 0) {
do {//清除不需要執行的節點
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//waitstatus必須為0或傳播。表明我們需要信號,但不要掛起。調用者重試以確保在掛起前無法獲取。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
復制代碼
然后看向下一個方法:
private final boolean parkAndCheckInterrupt(){
LockSupport.park(this);//掛起當前線程
return Thread.interrupted();//返回中斷標識
}
復制代碼
上面的取消獲取隊列里面的節點就不看了..cancelAcquire(node),里面就是取消正在進行的獲取嘗試。同時將無需的節點移除。當上面的操作走完之后就設置當前線程中斷標識。這里面主要流程是說如果加鎖不成功之后,對于當前線程是怎么執行操作的,我們可以看到,里面的方法中大部分在獲取不到鎖之后,下一步操作中會再次嘗試獲取下,如果獲取不到才會繼續執行,獲取到了我們就可以直接使用,這里也是多線程操作里面的魅力,每一個空隙中就可能會讓當前線程進行獲得鎖的操作。
釋放鎖操作
釋放鎖的步驟就簡單許多了:
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){
int c = getState() - releases;//同步狀態-當前釋放狀態值
if (Thread.currentThread() != getExclusiveOwnerThread())//如果當前線程不是拿鎖線程,則報監視器相關錯誤
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;//只有當前重入次數為0,才能返回true
setExclusiveOwnerThread(null);//當前獨占線程設為NULL
}
setState(c);//重新設置同步狀態
return free;
}
復制代碼
然后往下走:
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);//釋放當前線程
}
復制代碼從上面的順序往下面來看,我們主要發現線程在拿鎖階段是有許多的操作的,要根據線程的狀態再將線程從等待隊列中移除。釋放的時候就顯得簡潔了許多,我們只需要看到當前線程的狀態-1,然后看看是否是重入的。
我們通過一個簡單的重入鎖代碼可以看到,作者在用無鎖的操作去獲得鎖,這個整體的步驟里面考慮的東西很多,每一個時刻,線程都有可能千變萬化,我們需要了解的是我們每一個步驟都需要可能發生的情況。如果能夠考慮到發生的情況,那么有些步驟就可以直接跳過,我們直接就可以獲得最后的結果(這塊在線程嘗試獲鎖的階段可以體現)。有小伙伴對于重入鎖還有什么看法的可以在下面進行留言,我們可以相互學習,共同進步~