終于會了,java鎖的底層原理
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達(dá)
知識整理
Synchronized 內(nèi)置鎖,JVM級別
代碼優(yōu)化:同步代碼塊、減少鎖粒度、讀鎖并發(fā)
JDK自帶 偏置鎖、輕量級鎖(CAS操作)、自適應(yīng)自旋、鎖粗化、鎖消除
使用
底層 鎖升級過程、CAS操作的缺點(diǎn)【替換線程和copy mw】
優(yōu)化
Volatile
概念:非阻塞可見性、禁止指令重排序*
與syn區(qū)別: 無法實(shí)現(xiàn)原子操作、使用場景--單線程、不依賴當(dāng)前值
Reentrantlock 顯示鎖:基于AQS實(shí)現(xiàn),API級別
數(shù)據(jù)結(jié)構(gòu):state、waitstate【signal-1、傳播-3】、
獨(dú)占、共享 tryAcquireShared
AQS原理:
非公平鎖
特性鎖 可重入、輪詢、定時、可中斷
優(yōu)點(diǎn)、使用場景
與Syn區(qū)別、Syn優(yōu)點(diǎn)
死鎖
檢測算法:資源分配表、遍歷鎖關(guān)系圖
撤銷進(jìn)程、設(shè)置線程隨機(jī)優(yōu)先級
概念:多個線程因競爭資源而互相等待的僵局;4個必要條件:資源互斥、不可剝奪、保持與請求、循環(huán)等待
死鎖避免:鎖順序、鎖時限、死鎖檢測與恢復(fù)
死鎖檢測與恢復(fù):分配資源時不加條件;檢測時機(jī):進(jìn)程等待、定時、利用率下降
鎖模式
讀鎖、寫鎖
樂觀鎖:用戶解決---數(shù)據(jù)版本id、時間戳;CAS;適合寫操作少的場景;MVCC實(shí)現(xiàn)
悲觀鎖:數(shù)據(jù)庫行鎖、頁鎖...
synchronized的4種應(yīng)用方式 jvm內(nèi)部實(shí)現(xiàn) 稱為:內(nèi)置鎖
修飾類,作用范圍:synchronized括號內(nèi), 作用對象:類的所有對象;synchronized(Service.class){ }
修改靜態(tài)方法,作用范圍:整個靜態(tài)方法, 作用對象:類的所有對象;
修飾方法,被修飾的同步方法,作用范圍:整個方法, 作用對象:調(diào)用這個方法的對象;
缺點(diǎn):A線程執(zhí)行一個長時間任務(wù),B線程必須等待
修飾代碼塊,被修飾的代碼塊同步語句塊,作用范圍:大括號內(nèi)的代碼, 作用對象:調(diào)用這個代碼塊的對象;
優(yōu)點(diǎn):減少鎖范圍,耗時的代碼放外面,可以異步調(diào)用
notifyAll方法實(shí)現(xiàn)
鎖住(lock)
主->從 read load 將需要的數(shù)據(jù)從主內(nèi)存拷貝到自己的工作內(nèi)存(read and load)
修改 use assign 根據(jù)程序流程讀取或者修改相應(yīng)變量值(use and assign)
從->主 store write將自己工作內(nèi)存中修改了值的變量拷貝回主內(nèi)存(store and write)
釋放對象鎖(unlock)
當(dāng)多個線程訪問某個類,其始終能表現(xiàn)出正確的行為
采用了加鎖機(jī)制,當(dāng)一個線程訪問該類的某個數(shù)據(jù)時,進(jìn)行保護(hù),限制其他線程訪問,直到鎖釋放
Java中的鎖優(yōu)化 代碼方式、JDK自帶方式
減少鎖持有時間
使用同步代碼塊,而非同步方法;
減小鎖粒度
JDK1.6中 ConcurrentHashMap采取對segment加鎖而不是整個map加鎖,提高并發(fā)性;
鎖分離 讀鎖之間不互斥;讀寫分離
根據(jù)同步操作的性質(zhì),把鎖劃分為的讀鎖和寫鎖,讀鎖之間不互斥,提高了并發(fā)性
鎖主要存在四中狀態(tài),依次是:無鎖狀態(tài)01、偏向鎖狀態(tài)01、輕量級鎖狀態(tài)00、重量級鎖狀態(tài)10,
會隨著競爭的激烈而逐漸升級,鎖可以升級不可降級,提高 獲得鎖和釋放鎖 效率
“輕量級鎖”和“偏向鎖”作用:減少 獲得鎖和釋放鎖 的性能消耗
訪問Mark Word中偏向鎖的標(biāo)識是否設(shè)置成1,鎖標(biāo)志位是否為01,確認(rèn)為可偏向狀態(tài)。
如果為可偏向狀態(tài),則判斷偏向線程ID是否指向當(dāng)前線程,如果是,進(jìn)入步驟5,否則進(jìn)入步驟3。
如果線程ID并未指向當(dāng)前線程,則通過CAS操作競爭鎖。如果競爭成功,則將Mark Word中線程ID設(shè)置為當(dāng)前線程ID,然后執(zhí)行5;
如果競爭失敗,執(zhí)行4,偏向鎖升級為輕量級鎖。
如果CAS獲取偏向鎖失敗,則表示有競爭。當(dāng)?shù)竭_(dá)全局安全點(diǎn)(safepoint)時獲得偏向鎖的線程被掛起,偏向鎖升級為輕量級鎖,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼。(撤銷偏向鎖的時候會導(dǎo)致stop the word)
執(zhí)行同步代碼
虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲鎖對象目前的Mark Word的拷貝【因為棧幀為線程私有,對象大家都有】
拷貝對象頭中的Mark Word復(fù)制到鎖記錄(Lock Record)中;
拷貝成功后,虛擬機(jī)將使用CAS操作嘗試將對象的Mark Word中的,更新為指向Lock Record的指針,并將Lock record里的owner指針指向object mark word。如果更新成功,則執(zhí)行步驟4,否則執(zhí)行步驟5。
如果這個更新動作成功了,那么這個線程就擁有了該對象的鎖,并且對象Mark Word的鎖標(biāo)志位設(shè)置為“00”,即表示此對象處于輕量級鎖定狀態(tài),這時候線程堆棧與對象頭的狀態(tài)如圖所示。
如果一系列的連續(xù)操作都對同一個對象反復(fù)加鎖和解鎖,如循環(huán)體內(nèi),很耗性能
加鎖同步的范圍擴(kuò)展到整個操作序列的外部:第一個append到最后一個append;不對每個append加鎖
CAS底層實(shí)現(xiàn)原理 用于更新數(shù)據(jù)
適合資源競爭較少的情況,使用synchronized同步鎖進(jìn)行線程阻塞和喚醒切換以及用戶態(tài)內(nèi)核態(tài)間的切換操作額外浪費(fèi)消耗cpu資源;
CAS基于硬件實(shí)現(xiàn),不需要進(jìn)入內(nèi)核,不需要切換線程,操作自旋幾率較少,可以獲得更高的性能
synchronized在jdk1.6之后,已經(jīng)改進(jìn)優(yōu)化。synchronized的底層實(shí)現(xiàn)主要依靠Lock-Free的隊列,基本思路是自旋后阻塞,競爭切換后繼續(xù)競爭鎖,稍微犧牲了公平性,但獲得了高吞吐量。在線程沖突較少的情況下,可以獲得和CAS類似的性能;而線程沖突嚴(yán)重的情況下,性能遠(yuǎn)高于CAS
原子類中都使用到了CAS
volatile詳解
volatile保證新值立即同步到主存,
線程對變量讀取的時候,要從主內(nèi)存中讀,而不是緩存
變量的賦值一旦變化就會通知到其他線程,如果其他線程的工作內(nèi)存中存在這個同一個變量拷貝副本,那么其他線程會放棄這個副本中變量的值,重新去主內(nèi)存中獲取
為了減少CPU空閑時間,java不能保證程序執(zhí)行的順序與代碼中一致,
volatile修飾的變量相當(dāng)于生成內(nèi)存屏障,重排序時不能把后面的指令排到屏障之前;指令屏障
作用:為了保證happen-before原則:寫、鎖lock、傳遞性、線程啟動、中斷、終結(jié)、對象創(chuàng)建的先后關(guān)系
定義了線程、鎖、volatile變量、對象創(chuàng)建的先后關(guān)系
若滿足,則保證一個操作執(zhí)行的結(jié)果需要對另一個操作可見
判斷數(shù)據(jù)是否存在競爭、線程是否安全的依據(jù)
可重入鎖 Re entrantlock
無阻塞的同步機(jī)制(非公平鎖實(shí)現(xiàn))
可實(shí)現(xiàn)輪詢鎖、定時鎖、可中斷鎖特性;
提供了一個Condition(條件)類,對鎖進(jìn)行更精確的控制
默認(rèn)使用非公平鎖,可插隊跳過對線程隊列的處理(因此被稱為可重入)
ReentrantLock的內(nèi)部類Sync繼承了AQS,分為公平鎖FairSync和非公平鎖NonfairSync。
公平鎖:線程獲取鎖的順序和調(diào)用lock的順序一樣,F(xiàn)IFO;喚醒鎖的時間CPU浪費(fèi);是否是AQS隊列中的頭結(jié)點(diǎn)
非公平鎖:線程獲取鎖的順序和調(diào)用lock的順序無關(guān),先執(zhí)行l(wèi)ock方法的鎖不一定先獲得鎖
加鎖和解鎖都需要顯式寫出,實(shí)現(xiàn)了Lock接口,注意一定要在適當(dāng)時候unlock
總結(jié):公平鎖與非公平鎖對比
FairSync:lock()少了插隊部分(即少了CAS嘗試將state從0設(shè)為1,進(jìn)而獲得鎖的過程)
FairSync:tryAcquire(int acquires)多了需要判斷當(dāng)前線程是否在等待隊列首部的邏輯(實(shí)際上就是少了再次插隊的過程,但是CAS獲取還是有的)。
公平鎖的核心
獲取一次鎖數(shù)量,state值
如果鎖數(shù)量為0,如果當(dāng)前線程是等待隊列中的頭節(jié)點(diǎn),基于CAS嘗試將state(鎖數(shù)量)從0設(shè)置為1一次,如果設(shè)置成功,設(shè)置當(dāng)前線程為獨(dú)占鎖的線程;
如果鎖數(shù)量不為0或者當(dāng)前線程不是等待隊列中的頭節(jié)點(diǎn)或者上邊的嘗試又失敗了,查看當(dāng)前線程是不是已經(jīng)是獨(dú)占鎖的線程了,如果是,則將當(dāng)前的鎖數(shù)量+1;如果不是,則將該線程封裝在一個Node內(nèi),并加入到等待隊列中去。等待被其前一個線程節(jié)點(diǎn)喚醒。
非公平鎖 兩者都是非公平鎖
非公平鎖,可以直接插隊獲取鎖,跳過了對隊列的處理,速度會更快
公平鎖為了保證線程規(guī)規(guī)矩矩地排隊,需要增加阻塞和喚醒的時間開銷;
AQS底層原理:在lock獲取鎖時首先判斷當(dāng)前鎖是否可以用(AQS的state狀態(tài)值是否為0),如果是 直接“插隊”獲取鎖,否則進(jìn)入排隊隊列,并阻塞當(dāng)前線程;充分利用了喚醒線程的時間【Singel標(biāo)志喚醒,需要前驅(qū)節(jié)點(diǎn)喚醒】
非公平鎖加鎖的簡單步驟
如果設(shè)置成功,設(shè)置當(dāng)前線程為獨(dú)占鎖的線程;
如果設(shè)置失敗,還會再獲取一次鎖數(shù)量,---第二次插隊
如果鎖數(shù)量為0,再基于CAS嘗試將state(鎖數(shù)量)從0設(shè)置為1一次,如果設(shè)置成功,設(shè)置當(dāng)前線程為獨(dú)占鎖的線程;
如果鎖數(shù)量不為0或者上邊的嘗試又失敗了,查看當(dāng)前線程是不是已經(jīng)是獨(dú)占鎖的線程了,如果是,則將當(dāng)前的鎖數(shù)量+1;如果不是,則將該線程封裝在一個Node內(nèi),并加入到等待隊列中去。等待被其前一個線程節(jié)點(diǎn)喚醒
入隊后,無限循環(huán)tryAcquire(1)方法 ---第三次插隊
非公平鎖源碼
首先會使用addWaiter(Node.EXCLUSIVE)將當(dāng)前線程封裝進(jìn)Node節(jié)點(diǎn)node,然后將該節(jié)點(diǎn)加入等待隊列
先快速入隊【存在尾節(jié)點(diǎn),將使用CAS嘗試將尾節(jié)點(diǎn)設(shè)置為node】
如果快速入隊不成功【尾節(jié)點(diǎn)為空】,使用正常入隊方法enq,無限循環(huán)=第一次阻塞,直到Node節(jié)點(diǎn)入隊為止【創(chuàng)建一個dummy節(jié)點(diǎn),并將該節(jié)點(diǎn)通過CAS設(shè)置到頭節(jié)點(diǎn),若頭結(jié)點(diǎn)不為null,cas繼續(xù)快速入隊】
如果p不是頭節(jié)點(diǎn),或者tryAcquire(1)請求不成功,執(zhí)行shouldParkAfterFailedAcquire(Node pred, Node node)來檢測當(dāng)前節(jié)點(diǎn)是不是可以安全的被掛起:判斷p的等待狀態(tài)waitStatus
SIGNAL(即可以喚醒下一個節(jié)點(diǎn)的線程),則node節(jié)點(diǎn)的線程可以安全掛起,返回true
CANCELLED,則p的線程被取消了,我們會將p之前的連續(xù)幾個被取消的前驅(qū)節(jié)點(diǎn)從隊列中剔除
等待狀態(tài)是除了上述兩種的其他狀態(tài),CAS嘗試將前驅(qū)節(jié)點(diǎn)的等待狀態(tài)設(shè)為SIGNAL【p與node競爭】
take()和offer()都是lock了重入鎖,按照synchronized的公平鎖,兩個方法是互斥
take()方法需要等待1個小時才能返回,offer()需要馬上提交一個10秒后運(yùn)行的任務(wù),此時offer()可以插隊獲取鎖
原理:A執(zhí)行時,B lock()鎖,并休眠;當(dāng)鎖被A釋放處于可用狀態(tài)時,B線程卻還處于被喚醒的過程中,此時C線程請求鎖,可以優(yōu)先C得到鎖
顯示鎖可中斷,防止死鎖,內(nèi)置鎖不可中斷,會產(chǎn)生死鎖
實(shí)現(xiàn)其他特性的鎖
對鎖更精細(xì)的控制
顯示鎖易忘記 finally 塊釋放鎖,對程序有害
顯示鎖只能用在代碼塊,強(qiáng)制更細(xì)粒度的加鎖;syn可以用在方法上
synchronized 管理鎖定和釋放時,能標(biāo)識死鎖或者其他異常行為的來源,利于調(diào)試
Synchronized引入了偏向鎖,輕量級鎖(自旋鎖)后,兩者的性能就差不多
Condition類對鎖進(jìn)行更精確的控制,指定喚醒、分組喚醒
防止死鎖
輪詢鎖:用tryLock(long timeout, TimeUnit unit)和tryLock() 這兩個方法實(shí)現(xiàn),即沒有獲取到鎖,可以使用while循環(huán) 隔一段時間再次獲取,直到獲取到為止
定時鎖:指的是在指定時間內(nèi)沒有獲取到鎖,就取消阻塞并返回獲取鎖失??;tryLock(long timeout, TimeUnit unit)
可中斷鎖:lockInterruptibly,防止死鎖
區(qū)別
如果該鎖沒有被另一個線程持有,則獲取該鎖并立即返回,將鎖計數(shù)設(shè)置為 1;對應(yīng)AQS中的state
如果當(dāng)前線程已經(jīng)持有該鎖,將鎖計數(shù)加 1,并立即返回方法---重入鎖
如果該鎖被另一個線程持有,則禁用當(dāng)前線程,在獲得鎖之前,一直休眠,此時鎖保持計數(shù)設(shè)置為 1
tryLock能獲得鎖就返回true,不能就立即返回false,可以增加時間限制,如果超過該時間段還沒獲得鎖,返回false;tryLock(long timeout,TimeUnit unit),
lock能獲得鎖就返回true,不能的話一直等待獲得鎖
lockInterruptibly,中斷會拋出異常
鎖的Condition類
Condition中的await()方法相當(dāng)于Object的wait()方法
Condition中的signal()方法相當(dāng)于Object的notify()方法
Condition中的signalAll()相當(dāng)于Object的notifyAll()方法
ReentrantLock類可以喚醒指定條件的線程,而object的喚醒是隨機(jī)的
造成當(dāng)前線程在接到信號或被中斷之前一直處于等待狀態(tài) void await()
喚醒一個等待線程 void signal()
喚醒所有等待線程 void signalAll()
造成當(dāng)前線程在接到信號、被中斷或到達(dá)指定等待時間之前一直處于等待狀態(tài) boolean await(long time, TimeUnit unit)
造成當(dāng)前線程在接到信號、被中斷或到達(dá)指定等待時間之前一直處于等待狀態(tài) long awaitNanos(long nanosTimeout)
造成當(dāng)前線程在接到信號之前一直處于等待狀態(tài) void awaitUninterruptibly()
造成當(dāng)前線程在接到信號、被中斷或到達(dá)指定最后期限之前一直處于等待狀態(tài) boolean awaitUntil(Date deadline)
共享鎖實(shí)現(xiàn) 并發(fā)讀 ReentrantReadWriteLock、計數(shù)器
在AQS隊列中,將線程包裝為Node.SHARED節(jié)點(diǎn),即標(biāo)志為共享鎖
當(dāng)頭節(jié)點(diǎn)獲得共享鎖后,喚醒下一個共享類型結(jié)點(diǎn)的操作
頭節(jié)點(diǎn)node1調(diào)用unparkSuccessor()方法喚醒了Node2,并且調(diào)用tryAcquireShared方法檢查下一個節(jié)點(diǎn)是共享節(jié)點(diǎn)
如果是,更改頭結(jié)點(diǎn),重復(fù)以上步驟,以實(shí)現(xiàn)節(jié)點(diǎn)自身獲取共享鎖成功后,喚醒下一個共享類型結(jié)點(diǎn)的操作
死鎖
資源互斥條件:資源互斥,即某資源僅為一個進(jìn)程占有
資源不可剝奪條件:進(jìn)程所獲得的資源在未使用完畢之前,只能是主動釋放,不能被其他進(jìn)程強(qiáng)行奪走
保持和請求條件:進(jìn)程已經(jīng)保持了一個資源,又提出了新的資源請求,而該資源已被其他進(jìn)程占有
循環(huán)等待條件:進(jìn)程資源循環(huán)等待
如何避免死鎖
加鎖順序(線程按照一定的順序加鎖)
按照順序加鎖是一種死鎖預(yù)防機(jī)制,需要事先知道所有會用到的鎖
加鎖時限(超時則放棄)
獲取鎖時加上時限,超過時限則放棄請求,并釋放鎖,等待一段隨機(jī)的時間再重試
死鎖檢測與恢復(fù)
操作系統(tǒng)中:系統(tǒng)為進(jìn)程分配資源,不采取任何限制性措施,提供檢測和恢復(fù)的手段
死鎖恢復(fù)
撤消進(jìn)程,剝奪資源
線程設(shè)置優(yōu)先級,讓一個(或幾個)線程回退,剩下的線程就像沒發(fā)生死鎖一樣繼續(xù)保持著它們需要的鎖
死鎖發(fā)生的時候設(shè)置隨機(jī)的優(yōu)先級;如果賦予這些線程的優(yōu)先級是固定不變的,同一批線程總是會擁有更高的優(yōu)先級。
鎖模式包括:
共享鎖:(讀?。┯脩艨梢圆l(fā)讀取數(shù)據(jù),但不能獲取寫鎖,直到釋放所有讀鎖。
排他鎖(寫鎖):加上寫鎖后,其他線程無法加任何鎖;寫鎖可以讀和寫
更新鎖: 防止死鎖而設(shè)立,轉(zhuǎn)換讀鎖為寫鎖之前的準(zhǔn)備,僅一個線程可獲得更新鎖
行鎖: 粒度最小,并發(fā)性最高
頁鎖:鎖定一頁。25個行鎖可升級為一個頁鎖。
表鎖:粒度大,并發(fā)性低
數(shù)據(jù)庫鎖:控制整個數(shù)據(jù)庫操作
Happen-Before原則 八大原則:
定義了線程、鎖、volatile變量、對象創(chuàng)建的先后關(guān)系
若滿足,則保證一個操作執(zhí)行的結(jié)果需要對另一個操作可見
判斷數(shù)據(jù)是否存在競爭、線程是否安全的依據(jù)
單線程:在同一個線程中,書寫在前面的操作happen-before后面的操作。
鎖:解鎖先于鎖定;同一個鎖的unlock操作happen-before此鎖的lock操作。
volatile:先寫;對一個volatile變量的寫操作happen-before對此變量的任意操作(當(dāng)然也包括寫操作了)。
傳遞性原則:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
線程啟動:start方法優(yōu)先;同一個線程的start方法happen-before此線程的其它方法。
線程中斷:對線程interrupt方法的調(diào)用happen-before被中斷線程的檢測到中斷發(fā)送的代碼。
線程終結(jié):線程中的所有操作都happen-before線程的終止檢測。
對象創(chuàng)建:先初始化,后finalize;一個對象的初始化完成先于他的finalize方法調(diào)用。
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。
本文鏈接:
https://blog.csdn.net/zzpueye/article/details/90047506
鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布
??????
??長按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
