日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JUC锁-互斥锁ReentrantLock(二)

發布時間:2024/2/28 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JUC锁-互斥锁ReentrantLock(二) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

ReentrantLock介紹

ReentrantLock是一個可重入的互斥鎖,又被稱為“獨占鎖”。

顧名思義,ReentrantLock鎖在同一個時間點只能被一個線程鎖持有;而可重入的意思是,ReentrantLock鎖,可以被單個線程多次獲取。
ReentrantLock分為“公平鎖”和“非公平鎖”。它們的區別體現在獲取鎖的機制上是否公平。ReentrantLock在同一個時間點只能被一個線程獲取(當某線程獲取到“鎖”時,其它線程就必須等待);ReentraantLock是通過一個FIFO的等待隊列來管理獲取該鎖所有線程的。在“公平鎖”的機制下,線程依次排隊獲取鎖;而“非公平鎖”在鎖是可獲取狀態時,不管自己是不是在隊列的開頭都可能獲取鎖。

ReentrantLock的UML類圖

(01) ReentrantLock實現了Lock接口。

(02) ReentrantLock與sync是組合關系。ReentrantLock中,包含了Sync對象;而且,Sync是AQS的子類;更重要的是,Sync有兩個子類FairSync(公平鎖)和NonFairSync(非公平鎖)。ReentrantLock是一個獨占鎖,至于它到底是公平鎖還是非公平鎖,就取決于sync對象是”FairSync的實例”還是”NonFairSync的實例”。

ReentantLock的構造方法

final ReentrantLock lock = new ReentrantLock(); final ReentrantLock lock = new ReentrantLock(true) /** 同步器:內部類Sync的一個引用 */private final Sync sync;/*** 創建一個非公平鎖*/public ReentrantLock() {sync = new NonfairSync();}/*** 創建一個鎖* @param fair true-->公平鎖 false-->非公平鎖*/public ReentrantLock(boolean fair) {sync = (fair)? new FairSync() : new NonfairSync();}

三個內部類Sync/NonfairSync/FairSync,這里只列出類的定義

/*** 該鎖同步控制的一個基類.下邊有兩個子類:非公平機制和公平機制.使用了AbstractQueuedSynchronizer類的*/static abstract class Sync extends AbstractQueuedSynchronizer/*** 非公平鎖同步器*/final static class NonfairSync extends Sync/*** 公平鎖同步器*/final static class FairSync extends Sync

ReentantLock的FairSync的lock方法:

ReetantLock調用的lock方法調用了他的組件Sync的lock方法,先看一下FairSync的lock方法:

1.lock()
final void lock() {acquire(1); }
2.acquire()

acquire是在AQS實現的

public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt(); }

(01) “當前線程”首先通過tryAcquire()嘗試獲取鎖。獲取成功的話,直接返回;嘗試失敗的話,進入到等待隊列排序等待(前面還有可能有需要線程在等待該鎖)。

(02) “當前線程”嘗試失敗的情況下,先通過addWaiter(Node.EXCLUSIVE)來將“當前線程”加入到”CLH隊列(非阻塞的FIFO隊列)”末尾。CLH隊列就是線程等待隊列。

(03) 再執行完addWaiter(Node.EXCLUSIVE)之后,會調用acquireQueued()來獲取鎖。由于此時ReentrantLock是公平鎖,它會根據公平性原則來獲取鎖。

(04) “當前線程”在執行acquireQueued()時,會進入到CLH隊列中休眠等待,直到獲取鎖了才返回!如果“當前線程”在休眠等待過程中被中斷過,acquireQueued會返回true,此時”當前線程”會調用selfInterrupt()來自己給自己產生一個中斷。至于為什么要自己給自己產生一個中斷,后面再介紹。

我們分成四個部分去解釋這個acquire過程:

  • 一. tryAcquire()
  • 二. addWaiter()
  • 三. acquireQueued()
  • 四. selfInterrupt()

tryAcquire()

protected final boolean tryAcquire(int acquires) {// 獲取“當前線程”final Thread current = Thread.currentThread();// 獲取“獨占鎖”的狀態int c = getState();// c=0意味著“鎖沒有被任何線程鎖擁有”,if (c == 0) {// 若“鎖沒有被任何線程鎖擁有”,// 則判斷“當前線程”是不是CLH隊列中的第一個線程線程,// 若是的話,則獲取該鎖,設置鎖的狀態,并切設置鎖的擁有者為“當前線程”。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() 是通過判斷”當前線程”是不是在CLH隊列的隊首,setExclusiveOwnerThread()的作用就是,設置線程t為當前擁有“獨占鎖”的線程。

tryAcquire()的作用就是嘗試去獲取鎖。當 當前線程是隊首或者是正在執行的線程 則嘗試成功,返回true;否則嘗試失敗,返回false,后續再通過其它辦法來獲取該鎖。后面我們會說明,在嘗試失敗的情況下,是如何一步步獲取鎖的。

addWaiter()

private Node addWaiter(Node mode) {// 新建一個Node節點,節點對應的線程是“當前線程”,“當前線程”的鎖的模型是mode。Node node = new Node(Thread.currentThread(), mode);Node pred = tail;// 若CLH隊列不為空,則將“當前線程”添加到CLH隊列末尾if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 若CLH隊列為空,則調用enq()新建CLH隊列,然后再將“當前線程”添加到CLH隊列中。enq(node);return node; }

enq()

private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}} }

enq()的作用很簡單。如果CLH隊列為空,則新建一個CLH表頭;然后將node添加到CLH末尾。否則,直接將node添加到CLH末尾。

addWaiter()的作用,就是將當前線程添加到CLH隊列中。這就意味著將當前線程添加到等待獲取“鎖”的等待線程隊列中了。

acquireQueued()

//acquireQueued()的目的是從隊列中獲取鎖。 final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {// interrupted表示在CLH隊列的調度中,// “當前線程”在休眠時,有沒有被中斷過。boolean interrupted = false;for (;;) {// 獲取上一個節點。// node是“當前線程”對應的節點,這里就意味著“獲取上一個等待鎖的線程”。final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);} }

先把shouldParkAfterFailedAcquire(p, node)和parkAndCheckInterrupt()兩個函數弄懂

// 返回“當前線程是否應該阻塞” private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// 前繼節點的狀態int ws = pred.waitStatus;// 如果前繼節點是SIGNAL狀態,則意味這當前線程需要被unpark喚醒。此時,返回true。if (ws == Node.SIGNAL)return true;// 如果前繼節點是“取消”狀態,則設置 “當前節點”的 “當前前繼節點” 為 “‘原前繼節點’的前繼節點”。if (ws > 0) {do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 如果前繼節點為“0”或者“共享鎖”狀態,則設置前繼節點為SIGNAL狀態。compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false; }

(01) 關于waitStatus請參考下表(中擴號內為waitStatus的值),更多關于waitStatus的內容,可以參考前面的Node類的介紹。

CANCELLED[1] -- 當前線程已被取消 SIGNAL[-1] -- “當前線程的后繼線程需要被unpark(喚醒)”。一般發生情況是:當前線程的后繼線程處于阻塞狀態,而當前線程被release或cancel掉,因此需要喚醒當前線程的后繼線程。 CONDITION[-2] -- 當前線程(處在Condition休眠狀態)在等待Condition喚醒 PROPAGATE[-3] -- (共享鎖)其它線程獲取到“共享鎖” [0] -- 當前線程不屬于上面的任何一種狀態。

(02) shouldParkAfterFailedAcquire()通過以下規則,判斷“當前線程”是否需要被阻塞。

規則1:如果前繼節點狀態為SIGNAL,表明當前節點需要被unpark(喚醒),此時則返回true。 規則2:如果前繼節點狀態為CANCELLED(ws>0),說明前繼節點已經被取消,則通過先前回溯找到一個有效(非CANCELLED狀態)的節點,并返回false。 規則3:如果前繼節點狀態為非SIGNAL、非CANCELLED,則設置前繼的狀態為SIGNAL,并返回false

如果“規則1”發生,即“前繼節點是SIGNAL”狀態,則意味著“當前線程”需要被阻塞。接下來會調用parkAndCheckInterrupt()阻塞當前線程,直到當前先被喚醒才從parkAndCheckInterrupt()中返回。

parkAndCheckInterrupt方法

private final boolean parkAndCheckInterrupt() {// 通過LockSupport的park()阻塞“當前線程”。LockSupport.park(this);// 返回線程的中斷狀態。return Thread.interrupted(); }

parkAndCheckInterrupt()的作用是阻塞當前線程,并且返回“線程被喚醒之后”的中斷狀態。
它會先通過LockSupport.park()阻塞“當前線程”,然后通過Thread.interrupted()返回線程的中斷狀態。

了解了shouldParkAfterFailedAcquire()和parkAndCheckInterrupt()函數之后。我們接著分析acquireQueued()的for循環部分。

for (;;) {// 獲取上一個節點。// node是“當前線程”對應的節點,這里就意味著“獲取上一個等待鎖的線程”。final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}

說明:

(01) 通過node.predecessor()獲取前繼節點。predecessor()就是返回node的前繼節點,若對此有疑惑可以查看下面關于Node類的介紹。

(02) p == head && tryAcquire(arg)

  • 首先,判斷“前繼節點”是不是CHL表頭。如果是的話,則通過tryAcquire()嘗試獲取鎖。

  • 如果不是,那么我們需要shouldParkAfterFailedAcquire()我們判斷“當前線程”是否需要阻塞;為什么呢?

    (waitstate的狀態SIGNAL[-1] – “當前線程的后繼線程需要被unpark(喚醒)”。一般發生情況是:當前線程的后繼線程處于阻塞狀態,而當前線程被release或cancel掉,因此需要喚醒當前線程的后繼線程。)

  • 接著,“當前線程”阻塞的話,會調用parkAndCheckInterrupt()來阻塞線程。

  • 當線程被解除阻塞的時候,我們會返回線程的中斷狀態。而線程被解決阻塞,可能是由于“線程被中斷”,也可能是由于“其它線程調用了該線程的unpark()函數”。

(03) 再回到p==head這里。如果當前線程是因為其它線程調用了unpark()函數而被喚醒,那么喚醒它的線程,應該是它的前繼節點所對應的線程(關于這一點,后面在“釋放鎖”的過程中會看到)。 OK,是前繼節點調用unpark()喚醒了當前線程!

小結:acquireQueued()的作用就是“當前線程”會根據公平性原則進行阻塞等待,直到獲取鎖為止;并且返回當前線程在等待過程中有沒有并中斷過。

釋放公平鎖Unlock

1.unlock()

unlock()在ReentrantLock.java中實現的,而公平鎖和非公平鎖沒有重寫unlock方法,這說明釋放鎖的方法一樣

public void unlock() {sync.release(1); }

2. release()

public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false; }

3.tryRelease()

復制代碼protected final boolean tryRelease(int releases) {// c是本次釋放鎖之后的狀態int c = getState() - releases;// 如果“當前線程”不是“鎖的持有者”,則拋出異常!if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// 如果“鎖”已經被當前線程徹底釋放,則設置“鎖”的持有者為null,即鎖是可獲取狀態。if (c == 0) {free = true;setExclusiveOwnerThread(null);}// 設置當前線程的鎖的狀態。setState(c);return free; }

tryRelease()的作用是嘗試釋放鎖。

(01) 如果“當前線程”不是“鎖的持有者”,則拋出異常。

(02) 如果“當前線程”在本次釋放鎖操作之后,對鎖的擁有狀態是0(即,當前線程徹底釋放該“鎖”),則設置“鎖”的持有者為null,即鎖是可獲取狀態。同時,更新當前線程的鎖的狀態為0。

4.unparkSuccessor()

在release()中“當前線程”釋放鎖成功的話,會喚醒當前線程的后繼線程。
根據CLH隊列的FIFO規則,“當前線程”(即已經獲取鎖的線程)肯定是head;如果CLH隊列非空的話,則喚醒鎖的下一個等待線程。

private void unparkSuccessor(Node node) {// 獲取當前線程的狀態int ws = node.waitStatus;// 如果狀態<0,則設置狀態=0if (ws < 0)compareAndSetWaitStatus(node, ws, 0);//獲取當前節點的“有效的后繼節點”,無效的話,則通過for循環進行獲取。// 這里的有效,是指“后繼節點對應的線程狀態<=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); }

unparkSuccessor()的作用是“喚醒當前線程的后繼線程”。后繼線程被喚醒之后,就可以獲取該鎖并恢復運行了。

ReentantLock的NonFairSync的lock方法:

非公平鎖和公平鎖在獲取鎖的方法上,流程是一樣的;它們的區別主要表現在“嘗試獲取鎖的機制不同”。簡單點說,“公平鎖”在每次嘗試獲取鎖時,都是采用公平策略(根據等待隊列依次排序等待);而“非公平鎖”在每次嘗試獲取鎖時,都是采用的非公平策略(無視等待隊列,直接嘗試獲取鎖,如果鎖是空閑的,即可獲取狀態,則獲取鎖)。

我們已經詳細介紹了獲取公平鎖的流程和機制;下面,通過代碼分析以下獲取非公平鎖的流程。當染,我們主要強調他們不一樣的地方,

lock方法

final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1); }

lock()會先通過compareAndSet(0, 1)來判斷“鎖”是不是空閑狀態。是的話,“當前線程”直接獲取“鎖”;否則的話,調用acquire(1)獲取鎖。

“公平鎖”和“非公平鎖”關于lock()的對比

公平鎖 -- 公平鎖的lock()函數,會直接調用acquire(1)。 非公平鎖 -- 非公平鎖會先判斷當前鎖的狀態是不是空閑,是的話,就不排隊,而是直接獲取鎖。 也就是完成了我們說的插隊

acquire()

public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt(); }

“公平鎖”和“非公平鎖”關于acquire()的對比

公平鎖和非公平鎖,只有tryAcquire()函數的實現不同;即它們嘗試獲取鎖的機制不同。 這就是我們所說的“它們獲取鎖策略的不同所在之處”!

我們已經詳細介紹了acquire()涉及到的各個函數。
這里僅對它們有差異的函數tryAcquire()進行說明。

protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires); }final boolean nonfairTryAcquire(int acquires) {// 獲取“當前線程”final Thread current = Thread.currentThread();// 獲取“鎖”的狀態int c = getState();// c=0意味著“鎖沒有被任何線程鎖擁有”if (c == 0) {// 若“鎖沒有被任何線程鎖擁有”,則通過CAS函數設置“鎖”的狀態為acquires。// 同時,設置“當前線程”為鎖的持有者。if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {// 如果“鎖”的持有者已經是“當前線程”,// 則將更新鎖的狀態。int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false; }

“公平鎖”和“非公平鎖”關于tryAcquire()的對比

公平鎖和非公平鎖,它們嘗試獲取鎖的方式不同。 公平鎖在嘗試獲取鎖時,即使“鎖”沒有被任何線程鎖持有,它也會判斷自己是不是CLH等待隊列的表頭;是的話,才獲取鎖。 而非公平鎖在嘗試獲取鎖時,如果“鎖”沒有被任何線程持有,則不管它在CLH隊列的何處,它都直接獲取鎖。也就是“第二次插隊”

總結

以上是生活随笔為你收集整理的JUC锁-互斥锁ReentrantLock(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。