ipa去除时间锁_Java中的锁以及sychronized实现机制(十)
上節(jié)講了線程安全和原子性,其實就是并發(fā)代碼變成同步,意味著代碼只有一個人在使用,這樣就不會有問題。
(一)Java中的鎖
1.自旋鎖
為了不放棄CPU執(zhí)行時間,循環(huán)的使用CAS技術(shù)對數(shù)據(jù)嘗試進(jìn)行更新,直至成功。(樂觀鎖的實現(xiàn))
2.悲觀鎖
假定會發(fā)生并發(fā)沖突,同步所有對數(shù)據(jù)的相關(guān)操作,從讀數(shù)據(jù)就開始上鎖。(從讀數(shù)據(jù)就開始上鎖。)
3.樂觀鎖
假定沒有沖突,在修改數(shù)據(jù)時如果發(fā)生數(shù)據(jù)和之前獲取的不一致,則讀最新數(shù)據(jù),修改后重試修改。(假定能成功,如果不成功也是CAS的操作)
4.獨享鎖(寫)
給資源加上寫鎖,線程可以修改資源,其他線程不能再加鎖(單寫)(互斥鎖)(就 像談戀愛,被女朋友鎖定了,其他人無法獲取你)
5.共享鎖(讀)
給資源加上讀鎖只能讀不能改,其他線程只能加讀鎖,不能加寫鎖(多讀)(就像信息已經(jīng)放給婚介了,誰都可以獲取我的信息)
6.可重入鎖,不可重入鎖
線程拿到一把鎖之后,可以自由進(jìn)入同一把鎖所同步的其他代碼
public class ObjectSyncDemo2 {public synchronized void test1(Object arg) {
System.out.println(Thread.currentThread() + " 我開始執(zhí)行 " + arg);
if (arg == null) {
test1(new Object());
}
System.out.println(Thread.currentThread() + " 我執(zhí)行結(jié)束" + arg);
}
public static void main(String[] args) throws InterruptedException {
new ObjectSyncDemo2().test1(null);
}
}
7.公平鎖,非公平鎖
爭搶鎖的順序,如果按先來后到,則為公平。
公平鎖:線程1拿到鎖,線程2在等待,線程3也在等待,線程1執(zhí)行完釋放鎖,線程2就拿到這個鎖。
非公平鎖:線程1拿到鎖,線程2在等待,線程3也在等待,線程1執(zhí)行完釋放鎖,線程3就拿到這個鎖,本來應(yīng)該線程2拿到鎖的,線程3先拿到了。就是不公平。
(二)同步關(guān)鍵字synchronized
① 介紹
最基本的線程通信機(jī)制,基于對象監(jiān)視器實現(xiàn)的。JAVA中的每個對象都與一個監(jiān)視器相關(guān)聯(lián),線程可以鎖定或解鎖。一次只有一個線程可以鎖定監(jiān)視器。試圖鎖定該監(jiān)視器的任何其他線程都會被阻塞,知道它們可以獲得該監(jiān)視器上的鎖定為止。
② 特性
可重入,獨享,悲觀鎖。
③ 鎖的范圍
類鎖,對象鎖,鎖消除,鎖粗化
同步關(guān)鍵字,不僅是實現(xiàn)同步,根據(jù)JMM規(guī)定還能保證可見性(讀取最新主內(nèi)存數(shù)據(jù),結(jié)束后寫入主內(nèi)存)
④ 鎖消除
發(fā)生在編譯器級別的一種鎖優(yōu)化方式。完全不需要加鎖,卻執(zhí)行了加鎖操作。
// 鎖消除(jit)public class ObjectSyncDemo {
public void test3(Object arg) {
StringBuilder builder = new StringBuilder();
builder.append("a");
builder.append(arg);
builder.append("c");
System.out.println(arg.toString());
}
public void test2(Object arg) {
String a = "a";
String c = "c";
System.out.println(a + arg + c);
}
public void test1(Object arg) {
// jit 優(yōu)化, 消除了鎖
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("a");
stringBuffer.append(arg);
stringBuffer.append("c");
// System.out.println(stringBuffer.toString());
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000000; i++) {
new ObjectSyncDemo().test1("123");
}
}
}
test1()方法中的StringBuffer數(shù)以函數(shù)內(nèi)部的局部變量,進(jìn)作用于方法內(nèi)部,不可能逃逸出該方法,因此他就不可能被多個線程同時訪問,也就沒有資源的競爭,但是StringBuffer的append操作卻需要執(zhí)行同步操作
StringBuffer 的源碼
@Overridepublic synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
去除不可能存在共享資源競爭的鎖,通過鎖消除,可以節(jié)省毫無意義的請求鎖時間。
④ 鎖粗化
通常情況下,為了保證多線程間的有效并發(fā),會要求每個線程持有鎖的時間盡可能短,但是某些情況下,一個程序?qū)ν粋€鎖不間斷、高頻地請求、同步與釋放,會消耗掉一定的系統(tǒng)資源,因為鎖的講求、同步與釋放本身會帶來性能損耗,這樣高頻的鎖請求就反而不利于系統(tǒng)性能的優(yōu)化了,雖然單次同步操作的時間可能很短。鎖粗化就是告訴我們?nèi)魏问虑槎加袀€度,有些情況下我們反而希望把很多次鎖的請求合并成一個請求,以降低短時間內(nèi)大量鎖請求、同步、釋放帶來的性能損耗。
// 鎖粗化(運(yùn)行時 jit 編譯優(yōu)化)// jit 編譯后的匯編內(nèi)容, jitwatch可視化工具進(jìn)行查看
public class ObjectSyncDemo {
int i;
public void test1(Object arg) {
synchronized (this) {
i++;
}
synchronized (this) {
i++;
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000000; i++) {
new ObjectSyncDemo().test1("a");
}
}
}
在test1() 同步操作的代碼之間,需要做一些其它的工作,而這些工作只會花費很少的時間,那么我們就可以把這些工作代碼放入鎖內(nèi),將兩個同步代碼塊合并成一個,以降低多次鎖請求、同步、釋放帶來的系統(tǒng)性能消耗,合并后的代碼如下
前提中間不需要同步的代碼能夠很快速地完成,如果不需要同步的代碼需要花很長時間,就會導(dǎo)致同步塊的執(zhí)行需要花費很長的時間,這樣做也不合理。
public void test1(Object arg) {synchronized (this) {
i++;
i++;
}
}
(三)同步關(guān)鍵字加鎖原理
① JAVA對象頭
鎖的實現(xiàn)機(jī)制與java對象頭息息相關(guān),鎖的所有信息,都記錄在java的對象頭中。用2字(32位JVM中1字=32bit=4baye)存儲對象頭,如果是數(shù)組類型使用3字存儲(還需存儲數(shù)組長度)。對象頭中記錄了hash值、GC年齡、鎖的狀態(tài)、線程擁有者、類元數(shù)據(jù)的指針。
② 幾種鎖類型
線程的阻塞和喚醒需要CPU從用戶態(tài)轉(zhuǎn)為核心態(tài),頻繁的阻塞和喚醒對CPU來說是一件負(fù)擔(dān)很重的工作。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。
③ 偏向鎖(A線程獨占鎖,不用上下文切換。對象頭標(biāo)識)
大多數(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)。
④自旋鎖(一個線程嘗試獲取某個鎖時,如果該鎖已被其他線程占用,就一直循環(huán)檢測鎖是否被釋放,而不是進(jìn)入線程掛起或睡眠狀態(tài)。一定要有次數(shù)限制)
線程的阻塞和喚醒需要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等,在超出這個范圍后,自旋鎖會升級為阻塞鎖。
④ 輕量級鎖(A線程擁有鎖,B獲取,競爭,自旋)
加鎖
線程在執(zhí)行同步塊之前,JVM會先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,則自旋獲取鎖,當(dāng)自旋獲取鎖仍然失敗時,表示存在其他線程競爭鎖(兩條或兩條以上的線程競爭同一個鎖),則輕量級鎖會膨脹成重量級鎖。
解鎖
輕量級解鎖時,會使用原子的CAS操作來將Displaced Mark Word替換回到對象頭,如果成功,則表示同步過程已完成。如果失敗,表示有其他線程嘗試過獲取該鎖,則要在釋放鎖的同時喚醒被掛起的線程。
⑤ 重量級鎖(B線程自旋獲取不到鎖,膨脹重量鎖,阻塞A線程。直到B執(zhí)行完。)
重量鎖在JVM中又叫對象監(jiān)視器(Monitor),它很像C中的Mutex,除了具備Mutex(0|1)互斥的功能,它還負(fù)責(zé)實現(xiàn)了Semaphore(信號量)的功能,也就是說它至少包含一個競爭鎖的隊列,和一個信號阻塞隊列(wait隊列),前者負(fù)責(zé)做互斥,后一個用于做線程同步。
PS:有數(shù)據(jù)表明,除去大型互聯(lián)網(wǎng)公司,80%的系統(tǒng)不存在多線程的競爭的情況,一定要熟悉這幾種鎖,對以后面試鍍金(面試)真的很有用。
總結(jié)
以上是生活随笔為你收集整理的ipa去除时间锁_Java中的锁以及sychronized实现机制(十)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中读取文件编码_[转载]py
- 下一篇: java线程中的task_Java线程(