11.1 面試題
- 談?wù)勀銓?duì)Synchronized的理解
- Sychronized的鎖升級(jí)你聊聊
- Synchronized實(shí)現(xiàn)原理,monitor對(duì)象什么時(shí)候生成的?知道m(xù)onitor的monitorenter和monitorexit這兩個(gè)是怎么保證同步的嘛?或者說這兩個(gè)操作計(jì)算機(jī)底層是如何執(zhí)行的
- 偏向鎖和輕量級(jí)鎖有什么區(qū)別
11.2 Synchronized的性能變化
-
Java5以前,只有Synchronized,這個(gè)是操作系統(tǒng)級(jí)別的重量級(jí)操作
- 重量級(jí)鎖,假如鎖的競(jìng)爭(zhēng)比較激烈的話,性能下降
- Java 5之前 用戶態(tài)和內(nèi)核態(tài)之間的轉(zhuǎn)換
-
Java6 之后為了減少獲得鎖和釋放鎖所帶來的性能消耗,引入了輕量級(jí)鎖和偏向鎖
11.3 Synchronized鎖種類及升級(jí)步驟
11.3.1 多線程訪問情況
- 只有一個(gè)線程來訪問,有且唯一Only One
- 有兩個(gè)線程(2個(gè)線程交替訪問)
- 競(jìng)爭(zhēng)激烈,更多線程來訪問
11.3.2 升級(jí)流程
-
Synchronized用的鎖是存在Java對(duì)象頭里的MarkWord中,鎖升級(jí)功能主要依賴MarkWord中鎖標(biāo)志位和釋放偏向鎖標(biāo)志位
-
鎖指向,請(qǐng)牢記
- 偏向鎖:MarkWord存儲(chǔ)的是偏向的線程ID
- 輕量鎖:MarkWord存儲(chǔ)的是指向線程棧中Lock Record的指針
- 重量鎖:MarkWord存儲(chǔ)的是指向堆中的monitor對(duì)象(系統(tǒng)互斥量指針)
11.3.3 無鎖
11.3.4 偏鎖
偏向鎖:?jiǎn)尉€程競(jìng)爭(zhēng),當(dāng)線程A第一次競(jìng)爭(zhēng)到鎖時(shí),通過修改MarkWord中的偏向線程ID、偏向模式。如果不存在其他線程競(jìng)爭(zhēng),那么持有偏向鎖的線程將永遠(yuǎn)不需要進(jìn)行同步。
主要作用:
- 當(dāng)一段同步代碼一直被同一個(gè)線程多次訪問,由于只有一個(gè)線程那么該線程在后續(xù)訪問時(shí)便會(huì)自動(dòng)獲得鎖
- 同一個(gè)老顧客來訪,直接老規(guī)矩行方便
結(jié)論: - HotSpot的作者經(jīng)過研究發(fā)現(xiàn),大多數(shù)情況下:在多線程情況下,鎖不僅不存在多線程競(jìng)爭(zhēng),還存在由同一個(gè)線程多次獲得的情況,偏向鎖就是在這種情況下出現(xiàn)的,它的出現(xiàn)是為了解決只有一個(gè)線程執(zhí)行同步時(shí)提高性能。
- 偏向鎖會(huì)偏向于第一個(gè)訪問鎖的線程,如果在接下來的運(yùn)行過程中,該鎖沒有被其他線程訪問,則持有偏向鎖的線程將永遠(yuǎn)不需要出發(fā)同步。也即偏向鎖在資源在沒有競(jìng)爭(zhēng)情況下消除了同步語句,懶得連CAS操作都不做了,直接提高程序性能。
理論落地:
技術(shù)實(shí)現(xiàn):
偏向鎖JVM命令:
案例演示:
- 偏向鎖默認(rèn)情況演示—只有一個(gè)線程
public class SynchronizedUpDemo {
public static void main(String[] args) {
/**
* 這里偏向鎖在JDK6以上默認(rèn)開啟,開啟后程序啟動(dòng)幾秒后才會(huì)被激活,可以通過JVM參數(shù)來關(guān)閉延遲 -XX:BiasedLockingStartupDelay=0
*/
// try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
Object o = new Object();
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
偏向鎖的撤銷:
- 當(dāng)有另外一個(gè)線程逐步來競(jìng)爭(zhēng)鎖的時(shí)候,就不能再使用偏向鎖了,要升級(jí)為輕量級(jí)鎖,使用的是等到競(jìng)爭(zhēng)出現(xiàn)才釋放鎖的機(jī)制
- 競(jìng)爭(zhēng)線程嘗試CAS更新對(duì)象頭失敗,會(huì)等到全局安全點(diǎn)(此時(shí)不會(huì)執(zhí)行任何代碼)撤銷偏向鎖,同時(shí)檢查持有偏向鎖的線程是否還在執(zhí)行:
- 第一個(gè)線程正在執(zhí)行Synchronized方法(處于同步塊),它還沒有執(zhí)行完,其他線程來搶奪,該偏向鎖會(huì)被取消掉并出現(xiàn)鎖升級(jí),此時(shí)輕量級(jí)鎖由原來持有偏向鎖的線程持有,繼續(xù)執(zhí)行同步代碼塊,而正在競(jìng)爭(zhēng)的線程會(huì)自動(dòng)進(jìn)入自旋等待獲得該輕量級(jí)鎖
- 第一個(gè)線程執(zhí)行完Synchronized(退出同步塊),則將對(duì)象頭設(shè)置為無所狀態(tài)并撤銷偏向鎖,重新偏向。
題外話:Java15以后逐步廢棄偏向鎖,需要手動(dòng)開啟------->維護(hù)成本高
11.3.5 輕鎖
概念:多線程競(jìng)爭(zhēng),但是任意時(shí)候最多只有一個(gè)線程競(jìng)爭(zhēng),即不存在鎖競(jìng)爭(zhēng)太激烈的情況,也就沒有線程阻塞。
主要作用:有線程來參與鎖的競(jìng)爭(zhēng),但是獲取鎖的沖突時(shí)間極短---------->本質(zhì)是自旋鎖CAS
輕量鎖的獲?。?br>
案例演示:
自旋一定程度和次數(shù)(Java8 之后是自適應(yīng)自旋鎖------意味著自旋的次數(shù)不是固定不變的):
- 線程如果自旋成功了,那下次自旋的最大次數(shù)會(huì)增加,因?yàn)镴VM認(rèn)為既然上次成功了,那么這一次也大概率會(huì)成功
- 如果很少會(huì)自選成功,那么下次會(huì)減少自旋的次數(shù)甚至不自旋,避免CPU空轉(zhuǎn)
輕量鎖和偏向鎖的區(qū)別:
- 爭(zhēng)奪輕量鎖失敗時(shí),自旋嘗試搶占鎖
- 輕量級(jí)鎖每次退出同步塊都需要釋放鎖,而偏向鎖是在競(jìng)爭(zhēng)發(fā)生時(shí)才釋放鎖
11.3.6 重鎖
有大量線程參與鎖的競(jìng)爭(zhēng),沖突性很高
11.3.7 小總結(jié)
-
鎖升級(jí)的過程
-
鎖升級(jí)后,hashcode去哪兒了?
-
各種鎖優(yōu)缺點(diǎn)、synchronized鎖升級(jí)和實(shí)現(xiàn)原理
11.4 JIT編譯器對(duì)鎖的優(yōu)化
11.4.1 JIT
Just In Time Compiler 即時(shí)編譯器文章來源:http://www.zghlxwxcb.cn/news/detail-444396.html
11.4.2 鎖消除
/**
* 鎖消除
* 從JIT角度看想相當(dāng)于無視他,synchronized(o)不存在了
* 這個(gè)鎖對(duì)象并沒有被共用擴(kuò)散到其他線程使用
* 極端的說就是根本沒有加鎖對(duì)象的底層機(jī)器碼,消除了鎖的使用
*/
public class LockClearUpDemo {
static Object object = new Object();
public void m1() {
//鎖消除問題,JIT會(huì)無視它,synchronized(o)每次new出來的,都不存在了,非正常的
Object o = new Object();
synchronized (o) {
System.out.println("-----------hello LockClearUpDemo" + "\t" + o.hashCode() + "\t" + object.hashCode());
}
}
public static void main(String[] args) {
LockClearUpDemo lockClearUpDemo = new LockClearUpDemo();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
lockClearUpDemo.m1();
}, String.valueOf(i)).start();
}
}
}
/**
* -----------hello LockClearUpDemo 229465744 57319765
* -----------hello LockClearUpDemo 219013680 57319765
* -----------hello LockClearUpDemo 1109337020 57319765
* -----------hello LockClearUpDemo 94808467 57319765
* -----------hello LockClearUpDemo 973369600 57319765
* -----------hello LockClearUpDemo 64667370 57319765
* -----------hello LockClearUpDemo 1201983305 57319765
* -----------hello LockClearUpDemo 573110659 57319765
* -----------hello LockClearUpDemo 1863380256 57319765
* -----------hello LockClearUpDemo 1119787251 57319765
*/
11.4.3 鎖粗化
/**
* 鎖粗化
* 假如方法中首尾相接,前后相鄰的都是同一個(gè)鎖對(duì)象,那JIT編譯器會(huì)把這幾個(gè)synchronized塊合并為一個(gè)大塊
* 加粗加大范圍,一次申請(qǐng)鎖使用即可,避免次次的申請(qǐng)和釋放鎖,提高了性能
*/
public class LockBigDemo {
static Object objectLock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (objectLock) {
System.out.println("111111111111");
}
synchronized (objectLock) {
System.out.println("222222222222");
}
synchronized (objectLock) {
System.out.println("333333333333");
}
synchronized (objectLock) {
System.out.println("444444444444");
}
//底層JIT的鎖粗化優(yōu)化
synchronized (objectLock) {
System.out.println("111111111111");
System.out.println("222222222222");
System.out.println("333333333333");
System.out.println("444444444444");
}
}, "t1").start();
}
}
11.5 小總結(jié)
沒有鎖:自由自在
偏向鎖:一個(gè)線程
輕量鎖:兩個(gè)線程,CAS
重量鎖:多個(gè)線程,阻塞文章來源地址http://www.zghlxwxcb.cn/news/detail-444396.html
到了這里,關(guān)于并發(fā)編程11:Synchronized與鎖升級(jí)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!