【解析】ReentrantLock鎖、Syschronized鎖面試點解析

面試官提問

● 公平鎖與非公平鎖的區別是什么?

● 什么是可重入鎖?

● 什么是死鎖,怎樣避免死鎖?

● ReentrantLock與Syschronized實現原理是什么?兩者有什么區別?

● 請說明ReentrantLock獲取鎖與釋放鎖的流程。

● 請說明Syschronized鎖升級的過程。

● 鎖性能優化方法是什么?

● 介紹一下AbstractQueuedSynchronizer(AQS)。

1 公平鎖與非公平鎖

公平鎖是指多個線程競爭鎖時直接進入隊列排隊,根據申請鎖的順序獲得鎖,先到先得。而非公平鎖則是多個線程競爭鎖時,首先嘗試直接搶鎖,失敗后再進入等待隊列。

使用公平鎖,先到先得,線程獲取鎖時不會出現饑餓現象。使用非公平鎖,整體的吞吐效率比較高。

ReentrantLock默認是非公平鎖,在構造方法中傳入參數true則為公平鎖Synchronized是非公平鎖

2 可重入鎖

可重入鎖是指一個線程可以多次獲取同一把鎖,其實現原理是,為每個鎖關聯一個計數器,線程首次獲取鎖時,計數器置為1,再次獲取該鎖時,計數器加1;線程每退出同步塊一次,計數器就減1。計數器為0則代表鎖被當前線程釋放。

Synchronized和ReentrantLock都是可重入鎖。

3 ReentrantLock鎖

ReentrantLock鎖的特點是可重入,支持公平鎖和非公平鎖兩種方式。

閱讀ReentrantLock代碼可知,它主要利用CAS+AQS隊列來實現。以非公平鎖為例,當線程競爭鎖時首先使用CAS搶占鎖,成功則返回,失敗則進入AQS隊列并且掛起線程;當鎖被釋放時,喚醒AQS中的某個線程,從被掛起處再次嘗試獲取鎖(當AQS隊列頭節點的下一個節點不為空時,直接喚醒該節點;否則從隊尾向前遍歷,找到最后一個不為空的節點并喚醒),獲取鎖失敗則再次進入隊尾。圖1-13詳細描述了ReentrantLock非公平鎖的獲取與釋放流程。

圖1-13 ReentrantLock非公平鎖的獲取與釋放流程

下面通過源碼來分析ReentrantLock的實現。非公平鎖首先使用CAS檢測鎖是否空閑并搶占鎖,當多個線程同時搶占同一把鎖時,CAS操作保證只有一個線程執行成功。

final void lock() {//state為0則計數器設為1,表示搶占鎖成功if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);
}

假設3個線程T1、T2和T3同時競爭鎖,線程T1執行CAS成功,線程T2和T3則會進入acquire方法:

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

接下來分別閱讀tryAcquire、addWaiter和acquireQueued的實現代碼。

進入tryAcquire方法,若鎖空閑(state = 0),則當前線程通過CAS直接搶鎖,搶鎖成功則返回true;搶鎖失敗則判斷持有鎖的線程是否為自己,如果是自己的話就記錄重入鎖的次數,并返回獲取鎖成功,否則返回獲取鎖失敗。

    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) {//忽略AQS隊列中的等待線程,當前線程直接通過CAS搶鎖體現了非公平性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;}

若tryAcquie獲取鎖失敗,則執行addWaiter方法,線程加入AQS隊列尾部,具體代碼如下:

    private Node addWaiter(Node mode) {//初始化節點,模式設置為獨占Node node = new Node(Thread.currentThread(), mode);Node pred = tail;//tail不為null,說明隊列已被初始化if (pred != null) {node.prev = pred;//通過CAS將Node對象加入AQS隊列,成為尾節點if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}//隊列未初始化或者CAS操作失敗則進入enq函數enq(node);return node;}

T2和T3線程搶鎖失敗,假設它們同時加入AQS隊列,由于隊列尚未初始化(tail ==null),因此至少有一個線程進入enq()方法,代碼如下:

這段代碼通過自旋和CAS來實現非阻塞的原子操作,保證線程安全。假設T2和T3線程同時執行enq方法,**第一輪循環,CAS操作確保只有一個線程創建head節點;**第二輪循環,AQS隊列完成初始化,tail非空,T2和T3線程都進入else邏輯,通過CAS操作將當前節點加入隊尾。若T2線程執行compareAndSetTail成功,則T3線程需要在下一次循環時入隊,最終AQS隊列如圖1-14所示。

T2和T3線程進入隊列后執行acquireQueued()方法,AQS隊列頭節點的后繼節點可以再次嘗試獲取鎖,獲取鎖失敗后被掛起,代碼如下:

如果T1線程一直持有鎖,那么T2和T3線程最終會進入shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法,代碼如下:

最終T2和T3線程在狀態為Node.SIGNAL的前驅節點后掛起,保證前驅節點獲取鎖后能喚醒自己。AQS隊列中節點的狀態及說明如表1-1所示。

表1-1 AQS節點的狀態及說明

鎖的釋放過程比較簡單,代碼如下:

    public void unlock() {sync.release(1);}public final boolean release(int arg) {if (tryRelease(arg)) {//釋放鎖成功Node h = head;//喚醒AQS隊列中的某個節點(一般是頭節點)if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}

核心方法是tryRelease和unparkSuccessor,先看一下tryRelease的執行過程,代碼如下:

    protected final boolean tryRelease(int releases) {//重入鎖,每重入一次則state加1,每釋放鎖一次則state 減1int c = getState() - releases;// 若當前線程不是持有鎖的線程則拋出異常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;//state 減為 0,代表釋放鎖成功if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}

釋放鎖成功后會喚起AQS隊列中被掛起的線程,代碼如下:

    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) {// 如果節點為null或者處于取消狀態// 那就從后往前遍歷尋找距離頭節點最近的非取消節點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);
}

被喚醒的線程也不能保證搶鎖成功,失敗后依然會放置在隊尾,這里也體現了鎖的“非公平”性。

4 Syschronized鎖

在HotSpot虛擬機中,對象內存布局主要分為對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding),如圖1-15所示。

當線程訪問同步塊時,首先需要獲得鎖并把相關信息存儲在對象頭中,對象頭由以下兩部分組成:

● Mark Word:存儲自身運行時數據,例如HashCode、GC年齡、鎖相關信息等內容。MarkWord信息結構如表1-2所示。

表1-2 Mark Word信息結構表

● Klass Pointer:Class對象的類型指針,指向的位置是對象對應的Class對象(對應的元數據對象)的內存地址。

總體上來說,鎖升級過程如圖1-16所示。

1)偏向鎖

線程獲取偏向鎖的流程如下:

● 檢查Mark Word中的線程id。

● 若線程id為空,則通過CAS設置為當前線程id:成功則獲取鎖,失敗則撤銷偏向鎖。

● 若線程id不為空且為當前線程,則獲取鎖成功,否則撤銷偏向鎖

持有偏向鎖的線程每次進入這個鎖相關的同步塊時,只需判斷Mark Word中記錄的線程id是否為自己。在沒有競爭時,一個線程反復申請獲得同一把鎖,使用偏向鎖效率極高。

2)輕量級鎖

多個線程競爭偏向鎖導致鎖升級為輕量級鎖,獲取輕量級鎖的流程如下:

● 線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用于存儲鎖記錄的空間LockRecord,并將對象頭中的Mark Word復制到Lock Record。

● 利用CAS操作將對象頭中的Mark Word更新為指向Lock Record的指針,若操作成功則競爭到鎖,鎖標志位變為00,表示當前為輕量級鎖狀 態。

● CAS執行失敗且自旋一定次數后仍未成功,則說明該鎖已被其他線程搶占,這時輕量級鎖會膨脹為重量級鎖,鎖標志位變成為10。

使用輕量級鎖提升性能的前提**:多個線程交替執行同步塊,鎖在整個生命周期內基本不會存在競爭或者出現鎖競爭的概率很低,減少了使用重量級鎖產生的性能消耗。**


輕量級鎖與偏向鎖的比較:輕量級鎖每次申請、釋放都至少需要一次CAS操作,但偏向鎖只有在初始化時需要一次CAS操作,后續當前線程可以幾乎零成本地直接獲得鎖(僅需比較線程id是否為自己)。


3)自旋鎖

如果持有鎖的線程能在很短時間內釋放鎖,那么競爭鎖的線程就沒有必要阻塞掛起,它們只需要自旋等待持有鎖的線程釋放鎖,然后再嘗試獲取鎖,這樣就能減少傳統的重量級鎖因使用操作系統互斥量而產生的性能開銷。因此,在輕量級鎖膨脹為重量級鎖之前,一般會嘗試通過自旋的方式獲取鎖。假如當前持有鎖的線程一直不釋放鎖,那么自旋就是在無意義地浪費CPU時間,當自旋多次始終無法獲取鎖時,輕量級鎖會膨脹為重量級鎖。

4)重量級鎖

沒有競爭到鎖的線程會被掛起,只有在持有鎖的線程退出同步塊之后才會喚醒這些線程。喚醒操作涉及操作系統調度,開銷很大。

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

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

相關文章

04.Python代碼NumPy-通過索引或切片來訪問和修改

04.Python代碼NumPy-通過索引或切片來訪問和修改 提示&#xff1a;幫幫志會陸續更新非常多的IT技術知識&#xff0c;希望分享的內容對您有用。本章分享的是Python基礎語法。前后每一小節的內容是存在的有&#xff1a;學習and理解的關聯性&#xff0c;希望對您有用~ python語法…

跨平臺數據采集如何解決不同平臺之間的數據兼容性問題?

在數字化時代&#xff0c;企業越來越依賴多個信息系統來管理業務&#xff0c;例如ERP&#xff08;企業資源計劃&#xff09;、CRM&#xff08;客戶關系管理&#xff09;、財務管理系統、電商平臺等。然而&#xff0c;在進行跨平臺數據采集時&#xff0c;不同系統之間的數據格式…

解決 vite.config.ts 引入scss 預處理報錯

目錄 報錯1&#xff1a;[plugin:vite:css] [SASS] Error&#xff1a;Cant find stylesheet to import 報錯2&#xff1a;[plugin:vite:css] [sass] Error: Undefined variable 版本號&#xff1a; "sass": "^1.86.3","sass-loader": "^1…

C++筆記,數學函數

參考鏈接&#xff1a;C中數學函數的使用方法_cpp里指數函數-CSDN博客 頭文件 <cmath> 1. 基本的算數運算函數 1.1 sqrt() - 計算平方根 功能&#xff1a;計算一個非負實數的平方根。原型&#xff1a;double sqrt(double x);示例代碼&#xff1a; #include <iostr…

不關“貓”如何改變外網IP?3種免重啟切換IP方案

每次更換外網IP都要重啟路由器&#xff1f;太麻煩了&#xff01;那么&#xff0c;不關貓怎么改變外網IP&#xff1f;無論是為了網絡調試、爬蟲需求&#xff0c;還是解決IP限制問題&#xff0c;頻繁重啟設備既耗時又影響效率。其實&#xff0c;更換外網IP并不一定要依賴“重啟大…

道路運輸安全員企業負責人考試內容與范圍

道路運輸企業主要負責人&#xff08;安全員&#xff09;考證要求 的詳細說明&#xff0c;適用于企業法定代表人、分管安全負責人等需取得的 《道路運輸企業主要負責人和安全生產管理人員安全考核合格證明》&#xff08;交通運輸部要求&#xff09;。 考試內容與范圍 1. 法律法…

深入剖析 WiFi 定位解析功能:原理、技術優勢與應用場景

WiFi 定位解析功能的原理? 信號強度與距離的關系? WiFi 定位的核心原理基于無線信號傳播過程中的一個基本特性&#xff1a;信號強度與信號發射源&#xff08;即 WiFi 接入點&#xff0c;Access Point&#xff0c;簡稱 AP&#xff09;和接收設備之間距離的關聯。一般來說&am…

NVIDIA RTX? GPU 低成本啟動零售 AI 場景開發

零售行業正在探索應用 AI 升級客戶體驗&#xff0c;同時優化內部流程。面對多重應用場景以及成本優化壓力&#xff0c;團隊可采用成本相對可控的方案&#xff0c;來應對多重場景的前期項目預演和落地&#xff0c;避免短期內大規模投入造成的資源浪費。 客戶體驗 AI 場景的研究…

首次打藍橋杯總結(c/c++B組)

目錄 一、對每個題進行總結 1.填空題 2.第一個大題---可分解的正整數&#xff08;10--3&#xff09; 3.第二道大題---產值調整&#xff08;10--3&#xff09; 4.第三道大題---畫展部署&#xff08;15--7&#xff09; 5.第四道大題---水質檢測&#xff08;15--3&#x…

林納斯·托瓦茲:Linux系統之父 Git創始人

名人說&#xff1a;路漫漫其修遠兮&#xff0c;吾將上下而求索。—— 屈原《離騷》 創作者&#xff1a;Code_流蘇(CSDN)&#xff08;一個喜歡古詩詞和編程的Coder&#x1f60a;&#xff09; 林納斯托瓦茲&#xff1a;Linux之父、Git創始人 一、傳奇人物的誕生 1. 早年生活與家…

C語言多進程素數計算

題目描述&#xff1a; 以下代碼實現了一個多進程素數計算程序&#xff0c;通過fork()函數創建子進程來并行計算指定范圍內的素數。請仔細閱讀代碼并回答以下問題。 #include "stdio.h" #include "unistd.h" #include <sys/types.h> #include "…

uniapp-商城-27-vuex 通用方法

1 概述 上節說了vuex 的基本使用方法,分析了基本的使用方法。 在使用中,常見使用,我們要針對狀態,購物車,不同類事務的管理,如果按照上節課的通用方法,那么使用和維護是會很大的難度的。 所以這里就必須要進行處理,借助 modules 進行定義不同類事務的處理手段。便于…

半導體設備通信標準—secsgem v0.3.0版本使用說明文檔(4)之HSMS(SEMI E37)

文章目錄 1、消息快1.1、選擇 請求1.2、選擇響應1.3、取消選擇請求1.4、取消選擇響應1.5、Linktest 請求1.6、Linktest 響應1.7、拒絕請求1.8、單獨請求1.9、數據消息 2、 協議2.1、 事件 SEMI E37 HSMS 定義主機和設備之間通過 TCP 協議的通信。 它指定用于啟動和終止連接的數…

通過GO后端項目實踐理解DDD架構

最近在工作過程中重構的項目要求使用DDD架構&#xff0c;在網上查詢資料發現教程五花八門&#xff0c;并且大部分內容都是長篇的概念講解&#xff0c;晦澀難懂&#xff0c;筆者看了一些github上入門的使用DDD的GO項目&#xff0c;并結合自己開發中的經驗&#xff0c;談談自己對…

Ubuntu系統連網問題

0. Preface 給一臺新電腦裝上Ubuntu系統后&#xff0c;接好網線&#xff0c;發現上不了網&#xff0c;右上角是有網絡連接的圖標的&#xff0c;也能獲取到ip地址&#xff0c;就是沒辦法連網&#xff0c;ping www.google.com也沒反應。 其實應該是網絡設置有點問題&#xff0c;…

C/C++---頭文件保護機制

在 C 和 C 編程里&#xff0c;頭文件保護機制是一種防止頭文件被重復包含的技術&#xff0c;它主要借助 #ifndef、#define 和 #endif 這些預處理指令來達成&#xff0c;也可以使用 #pragma once 這一編譯器特定指令。下面詳細闡述這一機制&#xff1a; 1. 頭文件重復包含的問題…

藍橋杯 8. 分巧克力

分巧克力 原題目鏈接 問題描述 兒童節那天有 K 位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友們。 小明一共有 N 塊巧克力&#xff0c;其中第 i 塊是 H? W? 的長方形。為了公平起見&#xff0c;小明需要從這 N 塊巧克力中切出 K 塊巧克力分給小朋友們。 要求…

從 SQL2API 到 Text2API:開啟數據應用開發的新征程

在技術革新浪潮的席卷下&#xff0c;數據應用開發領域正經歷著深刻變革。曾經&#xff0c;構建數據 API 需要開發者具備扎實的數據庫知識和編程技能&#xff0c;手動編寫復雜的 SQL 查詢與 API 代碼&#xff0c;這一過程不僅耗時費力&#xff0c;還將眾多非技術人員阻擋在數據應…

繼承:(開始C++的進階)

我們今天來學習C的進階&#xff1a; 面向對象三大特性&#xff1a;封裝&#xff0c;繼承&#xff0c;多態。 封裝我們在前面已經學了&#xff0c;我們細細理解&#xff0c;我們的類的封裝&#xff0c;迭代器的封裝&#xff08;vector的迭代器可以是他的原生指針&#xff0c;li…

冒泡排序、插入排序、快速排序、堆排序、希爾排序、歸并排序

目錄 冒泡排序插入排序快速排序(未優化版本)快速排序(優化版本)堆排序希爾排序歸并排序各排序時間消耗對比 冒泡排序 冒泡排序核心邏輯就是對數組從第一個位置開始進行遍歷&#xff0c;如果發現該元素比下一個元素大&#xff0c;則交換位置&#xff0c;如果不大&#xff0c;就…