【源碼解析】從ReentrantLock角度聊聊AQS原理

AQS結構

		//頭節點 當前持有鎖的線程private transient volatile Node head;/*** Tail of the wait queue, lazily initialized.  Modified only via* method enq to add new wait node.*///每個進來的線程都插入到最后private transient volatile Node tail;/*** The synchronization state.*///代表當前鎖的狀態 0:表示沒有占用 大于0代表有線程持有當前鎖private volatile int state;//CAS設置值protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);}

AQS內部包裝成一個Node節點 通過state標識線程的狀態

    //等待線程被包裝成一個Node節點static final class Node {/** Marker to indicate a node is waiting in shared mode *///共享模式下static final Node SHARED = new Node();/** Marker to indicate a node is waiting in exclusive mode *///獨享模式下static final Node EXCLUSIVE = null;/** waitStatus value to indicate thread has cancelled *///線程取消爭搶這個鎖static final int CANCELLED =  1;/** waitStatus value to indicate successor's thread needs unparking *///當前node的后繼節點需要被喚醒static final int SIGNAL    = -1;/** waitStatus value to indicate thread is waiting on 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;}
		// 使用static,這樣每個線程拿到的是同一把鎖private static ReentrantLock reentrantLock = new ReentrantLock(true);public void createOrder() {// 比如我們同一時間,只允許一個線程創建訂單reentrantLock.lock();// 通常,lock 之后緊跟著 try 語句try {// 這塊代碼同一時間只能有一個線程進來(獲取到鎖的線程),// 其他的線程在lock()方法上阻塞,等待獲取到鎖,再進來// 執行代碼...} finally {// 釋放鎖// 釋放鎖必須要在finally里,確保鎖一定會被釋放,如果寫在try里面,發生異常,則有可能不會執行,就會發生死鎖reentrantLock.unlock();}}

Lock接口

public interface Lock {//加鎖void lock();//嘗試獲取鎖boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//釋放鎖void unlock();Condition newCondition();
}
		public class ReentrantLock implements Lock, java.io.Serializable public ReentrantLock() {sync = new NonfairSync();}public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

通過構造參數進行區分使用公平鎖還是非公平鎖。默認是非公平鎖。

		//繼承AQS 正在的獲取和釋放鎖是由Sync的實現類來控制的。abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = -5179523762034025860L;abstract void lock();final boolean nonfairTryAcquire(int acquires) {//xxxxxx}protected final boolean tryRelease(int releases) {//xxxx}}

Lock 加鎖

		//sync進行管理鎖//公平鎖static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;//爭搶鎖🔒final void lock() {acquire(1);}}

父類實現的acquire

    //lock.lock()public final void acquire(int arg) {//tryAcquire(arg) true 獲取鎖成功直接結束//如果沒有獲取到鎖,acquireQueued 會將線程壓入隊列中//!tryAcquire(arg)  沒有獲取到鎖,將當前線程掛起//addWaiterif (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}//因為FairSync實現了protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}
		protected final boolean tryAcquire(int acquires) {//獲取當前線程final Thread current = Thread.currentThread();int c = getState();// c == 0 當前沒有線程獲取鎖if (c == 0) {//當前是公平鎖,先來后到//查看隊列中是否有等待的線程//hasQueuedPredecessors 沒有的話才可以獲取線程//compareAndSetState CAS設置 有可能同時多個線程競爭鎖if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {//表示獲取到鎖了,標記以下setExclusiveOwnerThread(current);//執行到這里 表示獲取鎖成功return true;}}//當前有線程持有鎖,先判斷下 獲取線程的鎖是不是自己 也就是重入了//state += 1;else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;//checkif (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}//到這里表示沒有獲取到鎖//1.嘗試獲取失敗//2.也不是自己的可沖入鎖return false;}
    //隊列中有等待的線程 返回truepublic 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.Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}//CAS設置值protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);}protected final void setExclusiveOwnerThread(Thread thread) {//將當前線程設置為獲取到線程//當前擁有鎖的線程exclusiveOwnerThread = thread;}
    //此方法的作用是將線程包裝成node, 同時進入隊列中//EXCLUSIVE是獨占模式private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {//自己的前驅節點為隊尾節點node.prev = pred;//CAS更新if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}//有競爭鎖,加入隊列失敗enq(node);return node;}    //因為是公平鎖,所以會按照鏈表的形式將當前任務添加到隊列中final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {//獲取node的prev節點final Node p = node.predecessor();// p == head 說明當前節點雖然進到了阻塞隊列,但是是阻塞隊列的第一個,因為它的前驅是headif (p == head && tryAcquire(arg)) {// //到這里說明剛加入到等待隊列里面的node只有一個,并且此時獲取鎖成功,設置head為nodesetHead(node);p.next = null; // help GCfailed = false;return interrupted;}// // 到這里,說明上面的if分支沒有成功,要么當前node本來就不是隊頭,// // 要么就是tryAcquire(arg)沒有搶贏別人,繼續往下看if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}
//當前線程沒有搶到鎖 是否需要掛起當前線程//第一個參數是前驅節點 第二個節點是當前線程的節點private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;//前驅節點正常 -1 當前線程需要掛起if (ws == Node.SIGNAL)/** This node has already set status asking a release* to signal it, so it can safely park.*/return true;//        136     // 前驅節點 waitStatus大于0 ,之前說過,大于0 說明前驅節點取消了排隊。這里需要知道這點:
//        137     // 進入阻塞隊列排隊的線程會被掛起,而喚醒的操作是由前驅節點完成的。
//        138     // 所以下面這塊代碼說的是將當前節點的prev指向waitStatus<=0的節點,
//        139     // 簡單說,如果前驅節點取消了排隊,
//        140     // 找前驅節點的前驅節,往前循環總能找到一個waitStatus<=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;} 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.*/
//            // 仔細想想,如果進入到這個分支意味著什么
//            157         // 前驅節點的waitStatus不等于-1和1,那也就是只可能是0,-2,-3
//            158         // 在我們前面的源碼中,都沒有看到有設置waitStatus的,所以每個新的node入隊時,waitStatu都是0
//            159         // 用CAS將前驅節點的waitStatus設置為Node.SIGNAL(也就是-1)compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}//private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}

Lock 解鎖

線程如果沒有獲取到鎖,那么會掛起,LockSupport.park(this); 等待喚醒。

    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 boolean tryRelease(int arg) {throw new UnsupportedOperationException();}
        protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();//是否完全釋放鎖boolean free = false;// c == 0 沒有嵌套鎖住了 可以釋放if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}protected final void setExclusiveOwnerThread(Thread thread) {//將當前線程設置為獲取到線程//當前擁有鎖的線程exclusiveOwnerThread = thread;}
    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)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.*/// 喚醒后繼節點,但是有可能后繼節點取消了等待// 從隊尾往前找,找到waitStatus <= 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);}

這里存在并發問題:從前往后尋找不一定能找到剛剛加入隊列的后繼節點。

如果此時正有一個線程加入等待隊列的尾部,執行到上面第7行,第7行還未執行,解鎖操作如果從前面開始找 頭節點后面的第一個節點狀態為-1的節點,此時是找不到這個新加入的節點的,因為尾節點的next 還未指向新加入的node,但是從后面開始遍歷的話,那就不存在這種情況。

小結

鎖狀態,通過state標記, 0 沒有線程占用鎖,state >= 代表有線程獲取到鎖,> 1說明是可沖入鎖。 所以lock和unlock必須是配對的。

線程的阻塞和解決阻塞:lockSupport.park(thread)掛起線程,unpack()喚醒線程。

阻塞隊列: 爭搶的線程很多,所以需要將等待的線程通過一個queue進行管理這些線程。AQS是一個FIFO的隊列,

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/214737.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/214737.shtml
英文地址,請注明出處:http://en.pswp.cn/news/214737.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MLIR筆記(6)

5. 方言與操作 5.1. 方言的概念 在MLIR里&#xff0c;通過Dialect類來抽象方言。具體的每種方言都需要從這個基類派生一個類型&#xff0c;并實現重載自己所需的虛函數。 MLIR文檔里這樣描述方言&#xff08; MLIR Language Reference - MLIR&#xff09;&#xff1a; 方言…

手把手教你玩轉ESP8266(原理+驅動)

在嵌入式開發中&#xff0c;無線通信的方式有很多&#xff0c;其中 WIFI 是繞不開的話題。說到 WIFI 通信&#xff0c;就不得不提 ESP8266了。 ESP8266 是一款高性能的 WIFI 串口模塊&#xff0c;實現透明傳輸。只要有一定的串口知識&#xff0c;不需要知道 WIFI 原理就可以上…

作為一個產品經理帶你了解Axure的安裝和基本使用

1.Axure的簡介 Axure是一種強大的原型設計工具&#xff0c;它允許用戶創建交互式的、高保真度的原型&#xff0c;以及進行用戶體驗設計和界面設計。Axure可以幫助設計師和產品經理快速創建和共享原型&#xff0c;以便團隊成員之間進行溝通和反饋。Axure提供了豐富的交互組件和功…

Spring--10--Spring Bean的生命周期

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 1.Spring Bean1.1 什么是 Bean簡而言之&#xff0c;bean 是由 Spring IoC 容器實例化、組裝和管理的對象。 1.2 Spring框架管理Bean對象的優勢 2.Bean的生命周期實例…

西工大網絡空間安全學院計算機系統基礎實驗二(phase_2下——漫漫深夜過后的黎明!!!)

內存地址內存地址中的數注釋指向這塊內存的寄存器0xffffd0e8函數phase_2的棧幀0xffffd0e40xffffd0f4函數phase_2的棧幀0xffffd0e00x5655b7b0函數phase_2的棧幀0xffffd0dc0x565566ca函數read_six_numbers的返回地址&#xff0c;函數phase_2的棧幀0xffffd0d80x5655af64舊%ebx的值…

SpringIOC之ConditionEvaluator

博主介紹:?全網粉絲5W+,全棧開發工程師,從事多年軟件開發,在大廠呆過。持有軟件中級、六級等證書。可提供微服務項目搭建與畢業項目實戰,博主也曾寫過優秀論文,查重率極低,在這方面有豐富的經驗? 博主作品:《Java項目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+…

Netty性能好的原因是什么

Netty性能好的原因 廢話篇Netty性能好的原因是什么1. 非阻塞IO模型高效的Reactor線程模型零拷貝內存池設計無鎖串行化設計高性能序列化協議 廢話篇 相信有同學會經常被問到這樣的問題&#xff0c;不妨下次被面試官問到這種問題&#xff0c;我們可以這樣回答&#xff01; Nett…

簡單實用的firewalld命令

簡單實用的firewalld命令 一、查看防火墻是否打開二、查詢、開放、關閉端口三、查看已監聽端口四、驗證 一、查看防火墻是否打開 systemctl status firewalld ● firewalld.service - firewalld - dynamic firewall daemonLoaded: loaded (/usr/lib/systemd/system/firewalld.…

map.getOrDefault

map.getOrDefault 是 Java 中的一個方法&#xff0c;用于從 Map 中獲取指定鍵的值&#xff0c;如果鍵不存在&#xff0c;則返回指定的默認值。 方法簽名如下&#xff1a; V getOrDefault(Object key, V defaultValue) 其中&#xff0c;key 是要獲取值的鍵&#xff0c;defaul…

day19_java泛型

泛型 Java 泛型&#xff08;generics&#xff09;是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制&#xff0c;該機制允許程序員在編譯時檢測到非法的類型。保證了java的安全性 泛型的本質是參數化類型&#xff0c;也就是說所操作的數據類型被指定為一個參數…

AWS EC2使用 instance profile 訪問S3

AWS EC2 instance可以使用instance profile 配置訪問S3的權限。 然后就可以直接在EC2上執行 python代碼或者AWS CLI去訪問S3了。 唯一需要注意的地方是&#xff0c;申明region。 示例代碼&#xff1a; aws s3 ls xxxx-s3-bucket --region xxx-region import boto3 client …

一文讀懂MySQL基礎知識文集(8)

&#x1f3c6;作者簡介&#xff0c;普修羅雙戰士&#xff0c;一直追求不斷學習和成長&#xff0c;在技術的道路上持續探索和實踐。 &#x1f3c6;多年互聯網行業從業經驗&#xff0c;歷任核心研發工程師&#xff0c;項目技術負責人。 &#x1f389;歡迎 &#x1f44d;點贊?評論…

IDEA 報錯

IDEA 報錯&#xff1a; Cannot resolve symbol&#xff1a;這通常是由于 IDEA 無法識別您正在使用的類或方法導致的。請確保您已經導入了正確的包&#xff0c;并且您的類路徑設置正確。 NullPointerException&#xff1a;這通常是由于您的代碼嘗試訪問空對象或空值導致的。請檢…

JavaScript 函數的返回值

JavaScript 函數的返回值 JavaScript 函數的返回值是函數執行后返回的值&#xff0c;可以是任意類型的值&#xff0c;包括數字、字符串、布爾值、對象等。函數的返回值通過 return 關鍵字來指定&#xff0c;如果函數沒有指定返回值&#xff0c;則默認返回 undefined。例如&…

Qt處理焦點事件(獲得焦點,失去焦點)

背景&#xff1a; 我只是想處理焦點動作&#xff0c;由于懶&#xff0c;上網一搜&#xff0c;排名靠前的一位朋友&#xff0c;使用重寫部件的方式實現。還是因為懶&#xff0c;所以感覺復雜了。于是又花了一分鐘解決了一下。 所以記錄下來&#xff0c;以免以后忘了。 思路&a…

單目相機測距(3米范圍內)二維碼實現方案(python代碼 僅僅依賴opencv)

總體思路:先通過opencv 識別二維碼的的四個像素角位置,然后把二維碼的物理位置設置為 cv::Point3f(-HALF_LENGTH, -HALF_LENGTH, 0), //tl cv::Point3f(HALF_LENGTH, -HALF_LENGTH, 0), //tr cv::Point3f(HALF_LENGTH, HALF_LENGTH, 0), //br cv::P…

四年編程成長總結

文章目錄 計算機計算機基礎知識操作系統計算機網絡 自考學習與備考考試經歷 軟考學習與準備考試成果人生成長自主學習解決問題團隊合作 總結 計算機 計算機是我學習和應用Java編程的基礎&#xff0c;它為我提供了一個強大的工具和平臺。在這四年的學習中&#xff0c;我逐漸深入…

軟件運行原理 - 內存模型 - 棧內存

說明 C/C軟件運行時&#xff0c;內存根據使用方式的不同分為堆內存和棧內存&#xff0c;棧內存使用有以下特征&#xff1a; 棧內存使用&#xff08;申請、釋放&#xff09;由系統自動分配和釋放&#xff0c;程序員不用做任何操作。棧內存重復使用&#xff0c;進入函數時數據入…

什么是特征圖?

在卷積神經網絡&#xff08;CNN&#xff09;中&#xff0c;特征圖是在傳遞給卷積層的圖像上發生卷積操作后卷積層的輸出。 特征圖是如何形成的&#xff1f; 在上面的插圖中&#xff0c;我們可以看到特征圖是如何從提供的輸入圖像中形成的。 要發送到卷積層的圖像是一個包含像…

AutoSAR(基礎入門篇)1.2-AutoSAR的發展史

目錄 一、AutoSAR成員 二、AutoSAR歷史發展 三、未使用AutoSAR前的缺點 1、原始狀態