這篇文章主要介紹java中鎖機(jī)制的示例分析,文中介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們一定要看完!
成都創(chuàng)新互聯(lián)是一家專業(yè)從事成都網(wǎng)站建設(shè)、成都網(wǎng)站制作、網(wǎng)頁設(shè)計的品牌網(wǎng)絡(luò)公司。如今是成都地區(qū)具影響力的網(wǎng)站設(shè)計公司,作為專業(yè)的成都網(wǎng)站建設(shè)公司,成都創(chuàng)新互聯(lián)依托強(qiáng)大的技術(shù)實力、以及多年的網(wǎng)站運營經(jīng)驗,為您提供專業(yè)的成都網(wǎng)站建設(shè)、營銷型網(wǎng)站建設(shè)及網(wǎng)站設(shè)計開發(fā)服務(wù)!
何為同步?JVM規(guī)范規(guī)定JVM基于進(jìn)入和退出Monitor對象來實現(xiàn)方法同步和代碼塊同步,但兩者的實現(xiàn)細(xì)節(jié)不一樣。代碼塊同步是使用monitorenter和monitorexit指令實現(xiàn),而方法同步是使用另外一種方式實現(xiàn)的,細(xì)節(jié)在JVM規(guī)范里并沒有詳細(xì)說明,但是方法的同步同樣可以使用這兩個指令來實現(xiàn)。monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法結(jié)束處和異常處, JVM要保證每個monitorenter必須有對應(yīng)的monitorexit與之配對。任何對象都有一個 monitor 與之關(guān)聯(lián),當(dāng)且一個monitor 被持有后,它將處于鎖定狀態(tài)。線程執(zhí)行到 monitorenter 指令時,將會嘗試獲取對象所對應(yīng)的 monitor 的所有權(quán),即嘗試獲得對象的鎖。
鎖存在Java對象頭里。如果對象是數(shù)組類型,則虛擬機(jī)用3個Word(字寬)存儲對象頭,如果對象是非數(shù)組類型,則用2字寬存儲對象頭。在32位虛擬機(jī)中,一字寬等于四字節(jié),即32bit。
Java對象頭里的Mark Word里默認(rèn)存儲對象的HashCode,分代年齡和鎖標(biāo)記位。32位JVM的Mark Word的默認(rèn)存儲結(jié)構(gòu)如下:
在運行期間Mark Word里存儲的數(shù)據(jù)會隨著鎖標(biāo)志位的變化而變化。Mark Word可能變化為存儲以下4種數(shù)據(jù):
線程的阻塞和喚醒需要CPU從用戶態(tài)轉(zhuǎn)為核心態(tài),頻繁的阻塞和喚醒對CPU來說是一件負(fù)擔(dān)很重的工作。
Java SE1.6為了減少獲得鎖和釋放鎖所帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”,所以在Java SE1.6里鎖一共有四種狀態(tài),無鎖狀態(tài),偏向鎖狀態(tài),輕量級鎖狀態(tài)和重量級鎖狀態(tài),它會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。
大多數(shù)情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得。偏向鎖的目的是在某個線程獲得鎖之后,消除這個線程鎖重入(CAS)的開銷,看起來讓這個線程得到了偏護(hù)。另外,JVM對那種會有多線程加鎖,但不存在鎖競爭的情況也做了優(yōu)化,聽起來比較拗口,但在現(xiàn)實應(yīng)用中確實是可能出現(xiàn)這種情況,因為線程之前除了互斥之外也可能發(fā)生同步關(guān)系,被同步的兩個線程(一前一后)對共享對象鎖的競爭很可能是沒有沖突的。對這種情況,JVM用一個epoch表示一個偏向鎖的時間戳(真實地生成一個時間戳代價還是蠻大的,因此這里應(yīng)當(dāng)理解為一種類似時間戳的identifier)
偏向鎖的獲取
當(dāng)一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進(jìn)入和退出同步塊時不需要花費CAS操作來加鎖和解鎖,而只需簡單的測試一下對象頭的Mark Word里是否存儲著指向當(dāng)前線程的偏向鎖,如果測試成功,表示線程已經(jīng)獲得了鎖,如果測試失敗,則需要再測試下Mark Word中偏向鎖的標(biāo)識是否設(shè)置成1(表示當(dāng)前是偏向鎖),如果沒有設(shè)置,則使用CAS競爭鎖,如果設(shè)置了,則嘗試使用CAS將對象頭的偏向鎖指向當(dāng)前線程。
偏向鎖的撤銷
偏向鎖使用了一種等到競爭出現(xiàn)才釋放鎖的機(jī)制,所以當(dāng)其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有字節(jié)碼正在執(zhí)行),它會首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程不處于活動狀態(tài),則將對象頭設(shè)置成無鎖狀態(tài),如果線程仍然活著,擁有偏向鎖的棧會被執(zhí)行,遍歷偏向?qū)ο蟮逆i記錄,棧中的鎖記錄和對象頭的Mark Word,要么重新偏向于其他線程,要么恢復(fù)到無鎖或者標(biāo)記對象不適合作為偏向鎖,最后喚醒暫停的線程。
偏向鎖的設(shè)置
關(guān)閉偏向鎖:偏向鎖在Java 6和Java 7里是默認(rèn)啟用的,但是它在應(yīng)用程序啟動幾秒鐘之后才激活,如有必要可以使用JVM參數(shù)來關(guān)閉延遲-XX:BiasedLockingStartupDelay = 0。如果你確定自己應(yīng)用程序里所有的鎖通常情況下處于競爭狀態(tài),可以通過JVM參數(shù)關(guān)閉偏向鎖-XX:-UseBiasedLocking=false,那么默認(rèn)會進(jìn)入輕量級鎖狀態(tài)。
線程的阻塞和喚醒需要CPU從用戶態(tài)轉(zhuǎn)為核心態(tài),頻繁的阻塞和喚醒對CPU來說是一件負(fù)擔(dān)很重的工作。同時我們可以發(fā)現(xiàn),很多對象鎖的鎖定狀態(tài)只會持續(xù)很短的一段時間,例如整數(shù)的自加操作,在很短的時間內(nèi)阻塞并喚醒線程顯然不值得,為此引入了自旋鎖。
所謂“自旋”,就是讓線程去執(zhí)行一個無意義的循環(huán),循環(huán)結(jié)束后再去重新競爭鎖,如果競爭不到繼續(xù)循環(huán),循環(huán)過程中線程會一直處于running狀態(tài),但是基于JVM的線程調(diào)度,會出讓時間片,所以其他線程依舊有申請鎖和釋放鎖的機(jī)會。
自旋鎖省去了阻塞鎖的時間空間(隊列的維護(hù)等)開銷,但是長時間自旋就變成了“忙式等待”,忙式等待顯然還不如阻塞鎖。所以自旋的次數(shù)一般控制在一個范圍內(nèi),例如10,100等,在超出這個范圍后,自旋鎖會升級為阻塞鎖。
加鎖
線程在執(zhí)行同步塊之前,JVM會先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,則自旋獲取鎖,當(dāng)自旋獲取鎖仍然失敗時,表示存在其他線程競爭鎖(兩條或兩條以上的線程競爭同一個鎖),則輕量級鎖會膨脹成重量級鎖。
解鎖
輕量級解鎖時,會使用原子的CAS操作來將Displaced Mark Word替換回到對象頭,如果成功,則表示同步過程已完成。如果失敗,表示有其他線程嘗試過獲取該鎖,則要在釋放鎖的同時喚醒被掛起的線程。
重量鎖在JVM中又叫對象監(jiān)視器(Monitor),它很像C中的Mutex,除了具備Mutex(0|1)互斥的功能,它還負(fù)責(zé)實現(xiàn)了Semaphore(信號量)的功能,也就是說它至少包含一個競爭鎖的隊列,和一個信號阻塞隊列(wait隊列),前者負(fù)責(zé)做互斥,后一個用于做線程同步。
可重入鎖
本文里面講的是廣義上的可重入鎖,而不是單指JAVA下的ReentrantLock。
可重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數(shù)獲得鎖之后 ,內(nèi)層遞歸函數(shù)仍然有獲取該鎖的代碼,但不受影響。
在JAVA環(huán)境下 ReentrantLock 和synchronized 都是 可重入鎖。
public class Test implements Runnable{
public synchronized void get(){
System.out.println(Thread.currentThread().getId());
set();
}
public synchronized void set(){
System.out.println(Thread.currentThread().getId());
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
Test ss=new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
結(jié)果如下,是正確的,即同一個線程id被連續(xù)輸出兩次。
Threadid: 8
Threadid: 8
Threadid: 10
Threadid: 10
Threadid: 9
Threadid: 9
可重入鎖最大的作用是避免死鎖。
我們以自旋鎖作為例子。(注:自旋鎖,就是拿不到鎖的情況會不停自旋循環(huán)檢測來等待,不進(jìn)入內(nèi)核態(tài)沉睡,而是在用戶態(tài)自旋嘗試)
public class SpinLock {
private AtomicReference<Thread> owner =new AtomicReference<>();
public void lock(){
Thread current = Thread.currentThread();
while(!owner.compareAndSet(null, current)){
}
}
public void unlock (){
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}
上面是自旋鎖的一種實現(xiàn)。
對于自旋鎖來說:
1、若有同一線程兩調(diào)用lock() ,會導(dǎo)致第二次調(diào)用lock位置進(jìn)行自旋,產(chǎn)生了死鎖
說明這個鎖并不是可重入的。(在lock函數(shù)內(nèi),應(yīng)驗證線程是否為已經(jīng)獲得鎖的線程)
2、若1問題已經(jīng)解決,當(dāng)unlock()第一次調(diào)用時,就已經(jīng)將鎖釋放了。實際上不應(yīng)釋放鎖。
(采用計數(shù)次進(jìn)行統(tǒng)計)
修改之后,如下:
public class SpinLock1 {
private AtomicReference<Thread> owner =new AtomicReference<>();
private int count =0;
public void lock(){
Thread current = Thread.currentThread();
if(current==owner.get()) {
count++;
return ;
}
while(!owner.compareAndSet(null, current)){
}
}
public void unlock (){
Thread current = Thread.currentThread();
if(current==owner.get()){
if(count!=0){
count--;
}else{
owner.compareAndSet(current, null);
}
}
}
}
這種方式實現(xiàn)的自旋鎖即為可重入鎖。
另,看一下mutex的情況:
Mutex可以分為遞歸鎖(recursive mutex)和非遞歸鎖(non-recursive mutex)??蛇f歸鎖也可稱為可重入鎖(reentrant mutex),
非遞歸鎖又叫不可重入鎖(non-reentrant mutex)。
二者唯一的區(qū)別是,同一個線程可以多次獲取同一個遞歸鎖,不會產(chǎn)生死鎖。而如果一個線程多次獲取同一個非遞歸鎖,則會產(chǎn)生死鎖。
Windows下的Mutex和Critical Section是可遞歸的。
Linux下的pthread_mutex_t鎖默認(rèn)是非遞歸的??梢燥@示的設(shè)置PTHREAD_MUTEX_RECURSIVE屬性,將pthread_mutex_t設(shè)為遞歸鎖。
公平鎖
公平和非公平鎖的隊列都基于鎖內(nèi)部維護(hù)的一個雙向鏈表,表結(jié)點Node的值就是每一個請求當(dāng)前鎖的線程。公平鎖則在于每次都是依次從隊首取值。
鎖的實現(xiàn)方式是基于如下幾點:
表結(jié)點Node和狀態(tài)state的volatile關(guān)鍵字。
sum.misc.Unsafe.compareAndSet的原子操作(見附錄)。
非公平鎖
在等待鎖的過程中, 如果有任意新的線程妄圖獲取鎖,都是有很大的幾率直接獲取到鎖的。
以上是“java中鎖機(jī)制的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
分享名稱:java中鎖機(jī)制的示例分析
轉(zhuǎn)載源于:http://chinadenli.net/article38/gjedsp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供自適應(yīng)網(wǎng)站、品牌網(wǎng)站制作、網(wǎng)站建設(shè)、移動網(wǎng)站建設(shè)、全網(wǎng)營銷推廣、標(biāo)簽優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)