自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java并發(fā)編程:Synchronized 的實(shí)現(xiàn)原理

開(kāi)發(fā) 前端
隨著 JDK 的不斷發(fā)展,Synchronized 已經(jīng)做了足夠多的性能優(yōu)化。Synchronized 從一個(gè)開(kāi)銷(xiāo)很大的重量級(jí)鎖被優(yōu)化成一個(gè)可自動(dòng)適配場(chǎng)景的“智能”鎖,它可以根據(jù)場(chǎng)景轉(zhuǎn)換成偏向鎖、輕量級(jí)鎖,萬(wàn)不得已的情況下才會(huì)轉(zhuǎn)換成重量級(jí)鎖。

在剛開(kāi)始學(xué)習(xí) Java 并發(fā)編程的過(guò)程中,一遇到多線程,我們就會(huì)使用 synchronized 關(guān)鍵字。在 JDK1.5 之前,Synchronized 是一個(gè)重量級(jí)鎖,效率不盡如人意。JDK1.6 對(duì) Synchronized 鎖進(jìn)行了升級(jí)優(yōu)化,引入了偏向鎖和輕量級(jí)鎖,提高了獲取鎖和釋放鎖的效率。下面我們來(lái)看一看 Synchronized 的底層實(shí)現(xiàn)原理吧。

Synchronized 的底層實(shí)現(xiàn)原理

同步原理

我們先來(lái)反編譯下面的 method1 方法:

public void method1() {
    synchronized (this) {
        System.out.println("This is the synchronized");
    }
}

在下面,我們可以看到反編譯后的 method1 代碼:

圖片圖片

從上面代碼執(zhí)行過(guò)程中,我們看到代碼塊同步是使用 monitorenter 和 monitorexit 指令實(shí)現(xiàn)的。

monitorenter 指令是在編譯后插入到同步代碼塊的開(kāi)始位置,monitorexit 是插入到方法結(jié)束的位置或者異常處。

JVM 要保證每個(gè) monitorenter 必須有對(duì)應(yīng)的 monitorexit 與之配對(duì)。任何對(duì)象都有一個(gè) monitor 對(duì)象與之關(guān)聯(lián),當(dāng) monitor 被對(duì)象持有后,它將處于鎖定狀態(tài)。線程執(zhí)行到 monitorenter 指令時(shí),將會(huì)嘗試獲取對(duì)象所對(duì)應(yīng)的 monitor 的所有權(quán),即嘗試獲得對(duì)象的鎖。

我們?cè)倏匆幌?Synchronized 方法同步的步驟:

public synchronized void method2() {
    System.out.println("This is the synchronized method");
}

圖片圖片

在上面編譯過(guò)的 method2() 方法的執(zhí)行過(guò)程中,Synchronized 方法的同步是使用另外一種方式實(shí)現(xiàn)的,我們可以看到它被翻譯成普通的方法調(diào)用和返回 invokevirtual、return 指令。

一個(gè)被 Synchronized 修飾的方法在 JVM 底層并沒(méi)有與之對(duì)應(yīng)的字節(jié)碼指令。當(dāng)方法調(diào)用時(shí),調(diào)用指令將會(huì)檢查方法的 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志是否被設(shè)置,如果設(shè)置了執(zhí)行線程將先獲取 monitor,獲取成功之后,才能執(zhí)行方法,方法執(zhí)行完成再釋放 monitor。

Java 對(duì)象頭的概念

接下來(lái),我們?cè)賮?lái)了解一下 Java 對(duì)象頭的概念。Synchronized 用的鎖就是存放在 Java 對(duì)象頭里。如果對(duì)象是數(shù)組類(lèi)型,則虛擬機(jī)用 3 個(gè)字寬(Word)存儲(chǔ)對(duì)象頭,如果對(duì)象是非數(shù)組類(lèi)型,則用 2 字寬存儲(chǔ)對(duì)象頭。在 32 位虛擬機(jī)中,1 字寬等于 4 字節(jié),即 32bit。

我們看下面的表格,Java 對(duì)象頭主要包括兩部分?jǐn)?shù)據(jù):Mark Word(標(biāo)記字段)、Class Pointer(類(lèi)型指針)。Mark Word 用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),它是實(shí)現(xiàn)輕量級(jí)鎖和偏向鎖的關(guān)鍵。Class Pointer 是對(duì)象指向它的類(lèi)元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類(lèi)的實(shí)例。

圖片圖片

Java 對(duì)象頭里的 Mark Word 里默認(rèn)存儲(chǔ)對(duì)象的 HashCode、分代年齡和鎖標(biāo)記位。我們來(lái)看一下 32 位 JVM 的 Mark Word 的默認(rèn)存儲(chǔ)結(jié)構(gòu):

圖片圖片

在運(yùn)行期間,Mark Word 里存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化。Mark Word 可能變化為存儲(chǔ)以下 4 種數(shù)據(jù),我們來(lái)看一下存儲(chǔ)結(jié)構(gòu):

圖片圖片

在 64 位虛擬機(jī)下,Mark Word 是 64bit 大小的,我們看一下它的存儲(chǔ)結(jié)構(gòu):

圖片圖片

我們從上面的表格中看到了引入的偏向鎖和輕量級(jí)鎖。鎖級(jí)別從低到高依次為:無(wú)鎖狀態(tài)、偏向鎖狀態(tài)、輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài),這 4 種狀態(tài)是會(huì)隨著競(jìng)爭(zhēng)情況而逐漸升級(jí)的。鎖可以升級(jí)但不能降級(jí),目的是提高獲得鎖和釋放鎖的效率。

下面我們來(lái)了解一下鎖升級(jí)的流程。

鎖升級(jí)

Synchronized 內(nèi)部有一個(gè)隱藏的鎖升級(jí)流程,正是因?yàn)檫@個(gè)流程的存在,使得 Synchronized 得以發(fā)揮它的高性能特性。鎖升級(jí)中最重要的 2 個(gè)升級(jí)就是偏向鎖和輕量級(jí)鎖,下面我們分別展開(kāi)討論:

偏向鎖

通常情況下,鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得,為了讓線程更容易獲得鎖而引入了偏向鎖。

所謂偏向鎖,就是當(dāng)一個(gè)線程訪問(wèn)同步代碼塊并獲取鎖時(shí),會(huì)在對(duì)象頭存儲(chǔ)鎖偏向的線程 ID。這樣,以后該線程在進(jìn)入和退出同步塊時(shí),就不需要進(jìn)行 CAS 操作來(lái)加鎖和解鎖,只需要簡(jiǎn)單地測(cè)試一下對(duì)象頭的 Mark Word 里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖。

如果測(cè)試成功,則表示線程已經(jīng)獲得了鎖;如果測(cè)試失敗,則需要再測(cè)試一下 Mark Word 中偏向鎖的標(biāo)識(shí)是否設(shè)置成 1(表示當(dāng)前是偏向鎖)。如果沒(méi)有設(shè)置,則使用 CAS 競(jìng)爭(zhēng)鎖;如果設(shè)置了,則嘗試使用 CAS 將對(duì)象頭的偏向鎖指向當(dāng)前線程。

偏向鎖在 Java6 和 Java7 是默認(rèn)啟用的,但它在應(yīng)用程序啟動(dòng)幾秒鐘之后才會(huì)被激活,我們可以配置 JVM 參數(shù)來(lái)關(guān)閉延遲:-XX:BiasedLockingStartupDelay=0。如果確定應(yīng)用程序里所有的鎖通常情況下處于競(jìng)爭(zhēng)狀態(tài),我們可以配置如下的 JVM 參數(shù)關(guān)閉偏向鎖,之后程序默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài):

-XX:-UseBiasedLocking=false

輕量級(jí)鎖

輕量級(jí)鎖不是用來(lái)替代傳統(tǒng)的重量級(jí)鎖的,而是在沒(méi)有多線程競(jìng)爭(zhēng)的情況下,使用輕量級(jí)鎖能夠減少性能消耗,但是當(dāng)多個(gè)線程同時(shí)競(jìng)爭(zhēng)鎖時(shí),輕量級(jí)鎖會(huì)膨脹為重量級(jí)鎖。

我們先來(lái)看一下輕量級(jí)鎖的加鎖過(guò)程。在線程在執(zhí)行同步塊之前,JVM 會(huì)先在當(dāng)前線程的棧幀中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的 Mark Word 復(fù)制到鎖記錄中,官方稱為 Displaced Mark Word。然后線程嘗試使用 CAS 將對(duì)象頭中的 Mark Word 替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲

得鎖,如果失敗,表示其他線程競(jìng)爭(zhēng)鎖,當(dāng)前線程便嘗試使用自旋來(lái)獲取鎖。

輕量級(jí)解鎖時(shí),會(huì)使用原子的 CAS 操作將 Displaced Mark Word 替換回到對(duì)象頭,如果成功,則表示沒(méi)有競(jìng)爭(zhēng)發(fā)生。如果失敗,表示當(dāng)前鎖存在競(jìng)爭(zhēng),鎖就會(huì)膨脹成重量級(jí)鎖。

因?yàn)樽孕龝?huì)消耗 CPU,為了避免無(wú)用的自旋,一旦鎖升級(jí)到重量級(jí)鎖,就不會(huì)再恢復(fù)到輕量級(jí)鎖狀態(tài)。當(dāng)鎖處于這個(gè)狀態(tài)下,其他線程嘗試獲取鎖時(shí),都會(huì)被阻塞住,當(dāng)持有鎖的線程釋放鎖之后會(huì)喚醒這些線程,被喚醒的線程就會(huì)進(jìn)行新一輪的競(jìng)爭(zhēng)。

下圖是兩個(gè)線程同時(shí)爭(zhēng)奪鎖,導(dǎo)致輕量級(jí)鎖膨脹的流程。

圖片圖片

鎖的優(yōu)缺點(diǎn)對(duì)比

對(duì)于偏向鎖、輕量級(jí)鎖和重量級(jí)鎖這三者的優(yōu)缺點(diǎn),以及適用場(chǎng)景,我們可以通過(guò)下面的表格得到一個(gè)直觀的了解:

圖片圖片

總結(jié)

最后,我們來(lái)總結(jié)一下所講的主要內(nèi)容。首先,我們一起學(xué)習(xí)了 Synchronized 的底層實(shí)現(xiàn),Synchronized 作為一個(gè)關(guān)鍵字以它極簡(jiǎn)的語(yǔ)法也帶來(lái)了易讀性;之后,我?guī)懔私饬似蜴i的初始化、撤銷(xiāo)、關(guān)閉操作和輕量級(jí)鎖的加鎖、解鎖過(guò)程;最后,我?guī)惴治隽瞬煌i的優(yōu)缺點(diǎn)及適用場(chǎng)景,這些對(duì)你理解為什么 Synchronized 具備高性能是非常關(guān)鍵的。此外,不同鎖的,如偏向鎖、輕量級(jí)鎖對(duì)使用者來(lái)說(shuō)是透明的,這也體現(xiàn)了 Synchronized 的簡(jiǎn)單性。

隨著 JDK 的不斷發(fā)展,Synchronized 已經(jīng)做了足夠多的性能優(yōu)化。Synchronized 從一個(gè)開(kāi)銷(xiāo)很大的重量級(jí)鎖被優(yōu)化成一個(gè)可自動(dòng)適配場(chǎng)景的“智能”鎖,它可以根據(jù)場(chǎng)景轉(zhuǎn)換成偏向鎖、輕量級(jí)鎖,萬(wàn)不得已的情況下才會(huì)轉(zhuǎn)換成重量級(jí)鎖。它的應(yīng)用場(chǎng)景也隨著這些特性逐漸豐富起來(lái),在很多高并發(fā)場(chǎng)景甚至替代了 reentrantLock。

責(zé)任編輯:武曉燕 來(lái)源: 程序員技術(shù)充電站
相關(guān)推薦

2025-03-26 00:55:00

2017-02-27 10:43:07

Javasynchronize

2018-09-12 15:38:42

Javaatomic編程

2022-12-26 09:27:48

Java底層monitor

2021-03-10 15:59:39

JavaSynchronize并發(fā)編程

2017-12-06 16:28:48

Synchronize實(shí)現(xiàn)原理

2021-01-08 08:34:09

Synchronize線程開(kāi)發(fā)技術(shù)

2020-11-13 08:42:24

Synchronize

2019-05-27 08:11:13

高并發(fā)Synchronize底層

2020-08-23 10:03:51

SynchronizeJava

2021-07-04 08:01:30

Synchronize線程安全并發(fā)編程

2023-04-26 07:34:38

Java并發(fā)編程

2020-12-16 10:54:52

編程ForkJoin框架

2022-11-09 09:01:08

并發(fā)編程線程池

2020-12-09 08:21:47

編程Exchanger工具

2020-11-30 16:01:03

Semaphore

2020-12-04 19:28:53

CountDownLaPhaserCyclicBarri

2020-12-03 11:15:21

CyclicBarri

2023-10-18 09:27:58

Java編程

2020-09-22 12:00:23

Javahashmap高并發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)