做社群的網(wǎng)站有哪些西安百度推廣怎么做
你好,我是 shengjk1,多年大廠經(jīng)驗(yàn),努力構(gòu)建 通俗易懂的、好玩的編程語言教程。 歡迎關(guān)注!你會(huì)有如下收益:
- 了解大廠經(jīng)驗(yàn)
- 擁有和大廠相匹配的技術(shù)等
希望看什么,評(píng)論或者私信告訴我!
文章目錄
- 一、背景
- 二、線程之間通信的基本儲(chǔ)備
- 2.1 為什么線程之間需要通信
- 2.2 現(xiàn)代處理器結(jié)構(gòu)
- 2.3 基于現(xiàn)代處理器結(jié)構(gòu)下的線程運(yùn)行模型
- 2.4 線程工作內(nèi)存帶來的問題
- 三、volatile 和 synchronized
- 3.1 volatile是什么
- 3.1.1 可見性
- 3.1.2 禁止指令重排序優(yōu)化
- 3.1.3 內(nèi)存屏障(Memory Barrier)
- 3.1.4 不保證原子性
- 3.1.5 使用場(chǎng)景
- 3.2 volatile 原理和例子
- 3.2 synchronized 是什么
- 3.2.1. 基本用法
- 3.2.1.1 同步方法
- 3.2.1.2 同步代碼塊
- 3.2.2. 工作原理
- 3.2.3. 保證原子性和可見性
- 3.3 synchronized 原理和例子
- 四、總結(jié)
一、背景
前面兩篇,我們分別知道了 關(guān)于 java 多線程,你需要知道的一些基礎(chǔ)知識(shí) 以及 關(guān)于 java 多線程,你需要知道的一些基礎(chǔ)知識(shí)
本篇文章呢,我們繼續(xù)死磕 java 多線程,來聊一聊 java 多線程之間是如何通信的-volatile 和 synchronized
二、線程之間通信的基本儲(chǔ)備
2.1 為什么線程之間需要通信
在線程編程中,線程之間需要相互通信的主要原因是協(xié)作和數(shù)據(jù)共享。以下是一些主要的原因:
-
資源共享: 多個(gè)線程可能需要同時(shí)訪問和操作共享的資源,如數(shù)據(jù)結(jié)構(gòu)、文件、網(wǎng)絡(luò)連接等。為了避免數(shù)據(jù)競(jìng)爭(zhēng)和確保數(shù)據(jù)的一致性,線程之間需要通信來協(xié)調(diào)對(duì)共享資源的訪問。
-
任務(wù)分工: 在多線程編程中,不同線程可以負(fù)責(zé)不同的任務(wù)或子任務(wù),線程之間需要協(xié)作和通信以完成整體的工作。通信可以是單向的傳達(dá)結(jié)果,也可以是雙向的交換信息和狀態(tài)。
-
同步操作: 線程之間的通信可以用于同步操作,例如一個(gè)線程需要等待另一個(gè)線程完成某個(gè)任務(wù)后才能繼續(xù)執(zhí)行,或者通知其他線程某個(gè)事件已發(fā)生。
-
數(shù)據(jù)傳遞: 線程之間通過通信來傳遞數(shù)據(jù)和消息,以便于協(xié)調(diào)工作、共享信息或通知事件。
-
提高性能: 在某些情況下,通過線程之間的通信和協(xié)作可以提高程序的性能,例如使用線程池執(zhí)行并發(fā)任務(wù)時(shí),可以減少線程的創(chuàng)建和銷毀開銷。
-
實(shí)現(xiàn)某些模型和算法: 有些并發(fā)模型和算法需要線程之間的協(xié)作和通信,如生產(chǎn)者消費(fèi)者模型、并發(fā)隊(duì)列等。
綜上所述,線程之間的通信在多線程編程中是非常重要的,能夠?qū)崿F(xiàn)不同線程之間的協(xié)作、數(shù)據(jù)共享、任務(wù)分工等功能,從而有效利用計(jì)算資源并實(shí)現(xiàn)復(fù)雜的并發(fā)任務(wù)。
2.2 現(xiàn)代處理器結(jié)構(gòu)
一般情況下,現(xiàn)代處理器通常會(huì)包含一級(jí)緩存(L1 Cache)、二級(jí)緩存(L2 Cache)、和三級(jí)緩存(L3 Cache)。這些緩存一般都是集成在處理器芯片內(nèi)部的,被稱為“內(nèi)置緩存”(On-Chip Cache),用于加快數(shù)據(jù)的訪問速度和減少對(duì)主內(nèi)存的訪問。這里有一個(gè) 2020年的數(shù)據(jù)
L1 cache 訪問時(shí)間是 1ns
L2 cache 訪問時(shí)間是 4ns
主存訪問時(shí)間是 100ns
具體關(guān)于三級(jí)緩存(L3 Cache)通常的情況包括:
- 位置: L3 Cache通常位于處理器芯片內(nèi)部,但是相對(duì)于L1和L2 Cache,它的容量更大,從幾MB到幾十MB不等。
- 作用: L3 Cache主要用來緩存多個(gè)核心之間共享的數(shù)據(jù),以提高處理器核心之間的數(shù)據(jù)共享效率。
- 互相之間是否獨(dú)立: 不同處理器核心之間可以訪問共享的L3 Cache,這有助于提高多個(gè)核心之間數(shù)據(jù)訪問的效率。
由于L3 Cache容量較大且能夠?yàn)槎鄠€(gè)核心提供共享數(shù)據(jù)緩存,因此它可以有效地提高多核處理器系統(tǒng)中核心之間的數(shù)據(jù)共享和協(xié)作效率。雖然L3 Cache通常也集成在處理器芯片內(nèi)部,但某些處理器架構(gòu)可能會(huì)將部分L3 Cache放置在處理器芯片外部以適應(yīng)不同的需求。
當(dāng)系統(tǒng)運(yùn)行時(shí),CPU執(zhí)行計(jì)算的過程如下:
- 程序以及數(shù)據(jù)被加載到主內(nèi)存
- 指令和數(shù)據(jù)被加載到CPU緩存
- CPU執(zhí)行指令,把結(jié)果寫到高速緩存
- 高速緩存中的數(shù)據(jù)寫回主內(nèi)存
2.3 基于現(xiàn)代處理器結(jié)構(gòu)下的線程運(yùn)行模型
線程的工作內(nèi)存其實(shí)就是多級(jí)緩存以及CPU寄存器的抽象。
所以每個(gè)線程在運(yùn)行時(shí)確實(shí)都會(huì)有自己的一套完整的上下文,這個(gè)上下文包含了線程當(dāng)前的狀態(tài)、執(zhí)行環(huán)境以及必要的數(shù)據(jù)。這個(gè)上下文在線程切換時(shí)被保存和恢復(fù),以確保線程可以從中斷點(diǎn)繼續(xù)執(zhí)行。下面是上下文的一些重要組成部分:
-
寄存器狀態(tài): 在上下文中,包含了線程當(dāng)前寄存器的狀態(tài)。寄存器存儲(chǔ)了當(dāng)前線程的執(zhí)行環(huán)境,包括程序計(jì)數(shù)器、棧指針、各種通用寄存器等。
-
棧: 程序使用的棧包含了函數(shù)調(diào)用、局部變量以及其他執(zhí)行環(huán)境的信息。每個(gè)線程都有自己的棧,用于存儲(chǔ)執(zhí)行過程中的臨時(shí)數(shù)據(jù)。
-
程序計(jì)數(shù)器(Program Counter): 程序計(jì)數(shù)器存儲(chǔ)著當(dāng)前執(zhí)行的指令地址或者是下一條待執(zhí)行的指令地址。
-
線程狀態(tài): 這部分描述了線程當(dāng)前所處的狀態(tài),比如運(yùn)行態(tài)、就緒態(tài)、阻塞態(tài)等。線程狀態(tài)的改變會(huì)觸發(fā)線程調(diào)度。
-
權(quán)限和訪問控制: 上下文中可能包含了線程的權(quán)限信息,以確保線程的操作在合適的權(quán)限下進(jìn)行。
-
堆棧指針信息: 記錄了當(dāng)前線程堆棧的指針位置,在進(jìn)行棧操作時(shí)起著重要作用。
-
資源分配信息: 上下文還可能包括了線程已獲得的資源、線程使用的棧大小等信息。
通過保存和恢復(fù)這些上下文信息,操作系統(tǒng)能夠有效地管理多個(gè)線程的執(zhí)行,實(shí)現(xiàn)線程之間的切換和并發(fā)執(zhí)行。這些上下文信息是確保線程能夠在不同執(zhí)行環(huán)境下正確運(yùn)行的關(guān)鍵。
2.4 線程工作內(nèi)存帶來的問題
線程有自己的工作內(nèi)存最大的問題之一就是數(shù)據(jù)不一致性問題。這種情況可以導(dǎo)致程序出現(xiàn)難以預(yù)測(cè)的錯(cuò)誤行為,因?yàn)槎鄠€(gè)線程可能會(huì)在各自的工作內(nèi)存中緩存共享變量的副本,而這些副本的更新可能無法即時(shí)反映到其他線程中,引發(fā)數(shù)據(jù)的不一致性。
數(shù)據(jù)不一致性問題的一些常見情形包括:
-
寫問題(Write Problem): 一個(gè)線程在自己的工作內(nèi)存中修改了共享變量但未立即寫回主內(nèi)存,導(dǎo)致其他線程無法立即看到這個(gè)變化。
-
讀問題(Read Problem): 一個(gè)線程從主內(nèi)存中讀取了共享變量的值,但由于另一個(gè)線程已經(jīng)對(duì)該變量進(jìn)行了修改,該線程的工作內(nèi)存中的值已經(jīng)過時(shí),導(dǎo)致讀取的值不正確。
-
指令重排序問題: 編譯器或處理器可能會(huì)對(duì)指令進(jìn)行重排序優(yōu)化,這可能導(dǎo)致線程在不同的順序下訪問共享變量,進(jìn)而使得數(shù)據(jù)出現(xiàn)不一致的情況。
其實(shí)就是兩類問題:
- 數(shù)據(jù)可見性的問題,即另外一個(gè)線程改了共享變量,我能不能馬上知道。
- 共享變量一致性的問題,即多個(gè)線程操作同一個(gè)共享變量,它的結(jié)果能不能跟非多線程操作同一個(gè)共享變量的結(jié)果一致。
為了解決上述的兩個(gè)問題,我們引入了 volatile 和 synchronized
三、volatile 和 synchronized
3.1 volatile是什么
volatile
是 Java 中的一個(gè)關(guān)鍵字,主要用于確保多線程環(huán)境下變量的可見性和有序性。當(dāng)一個(gè)變量被聲明為 volatile
時(shí),意味著這個(gè)變量可能會(huì)被多個(gè)線程同時(shí)訪問和修改,因此需要使用 volatile
來確保所有線程都能看到變量的最新值。
下面是 volatile
的詳細(xì)解釋:
3.1.1 可見性
在多線程環(huán)境中,一個(gè)線程修改了一個(gè)共享變量的值,另一個(gè)線程無法立即看到這個(gè)變化,除非它主動(dòng)讀取該變量的值。而 volatile
關(guān)鍵字保證了當(dāng)一個(gè)線程修改了一個(gè) volatile
變量的值后,其他線程會(huì)立即看到這個(gè)變化。這是因?yàn)?volatile
變量不會(huì)被緩存到線程的工作內(nèi)存中,每次讀取 volatile
變量時(shí)都會(huì)直接從主內(nèi)存中獲取最新值。
3.1.2 禁止指令重排序優(yōu)化
編譯器和處理器可能會(huì)對(duì)代碼進(jìn)行指令重排序優(yōu)化,以提高執(zhí)行效率。但是,在某些情況下,這種重排序可能會(huì)導(dǎo)致多線程環(huán)境下的數(shù)據(jù)不一致問題。volatile
關(guān)鍵字可以防止這種情況發(fā)生,它會(huì)告訴編譯器和處理器不要對(duì)這個(gè)變量進(jìn)行重排序優(yōu)化。
3.1.3 內(nèi)存屏障(Memory Barrier)
現(xiàn)代處理器為了提高性能會(huì)對(duì)內(nèi)存操作進(jìn)行優(yōu)化,包括重新排序讀寫操作。為了強(qiáng)制處理器的操作符合程序員的預(yù)期順序,編譯器會(huì)在使用volatile的讀/寫操作周圍插入內(nèi)存屏障(Memory Barrier)。這些內(nèi)存屏障是一種同步機(jī)制,用于阻止屏障兩側(cè)的指令被重排序。通過確保特定的讀寫操作順序執(zhí)行,內(nèi)存屏障確保了程序中的某些操作在特定時(shí)刻已經(jīng)執(zhí)行完成或已經(jīng)啟動(dòng)執(zhí)行,從而在多線程環(huán)境中維持操作的有序性。
3.1.4 不保證原子性
雖然 volatile
可以保證可見性和禁止指令重排序優(yōu)化,但它不能保證復(fù)合操作(如自增或自減等)的原子性。如果需要一個(gè)變量在多線程環(huán)境下的原子性操作,如計(jì)數(shù)或狀態(tài)更新等,應(yīng)該使用 AtomicInteger
或 AtomicLong
等原子類或者使用 synchronized
塊來保證原子性。但對(duì)于簡(jiǎn)單的狀態(tài)標(biāo)記(例如一個(gè)標(biāo)志位),volatile
是足夠的。
3.1.5 使用場(chǎng)景
volatile
主要用于以下場(chǎng)景:
- 狀態(tài)標(biāo)記:例如用于標(biāo)記某個(gè)任務(wù)是否已經(jīng)完成。在這種情況下,一個(gè)線程會(huì)檢查這個(gè)標(biāo)記,然后基于這個(gè)標(biāo)記的值來決定下一步行動(dòng)。如果這個(gè)標(biāo)記在多線程環(huán)境下被使用,并且可能被多個(gè)線程同時(shí)修改,那么就需要使用
volatile
來確保每個(gè)線程都能看到最新的標(biāo)記值。 - 單例模式的雙重檢查鎖定:在某些單例模式的實(shí)現(xiàn)中,會(huì)使用
volatile
來確保實(shí)例在多個(gè)線程間的正確創(chuàng)建和訪問。
總的來說,volatile
是一個(gè)輕量級(jí)的同步機(jī)制,用于確保多線程環(huán)境下變量的可見性和有序性。但在需要復(fù)雜同步操作或原子性保證的情況下,可能需要考慮使用更強(qiáng)大的同步機(jī)制,如 synchronized
塊或鎖。
3.2 volatile 原理和例子
volatile 狀態(tài)標(biāo)記 的例子:
public class StopThreadExample {// 使用volatile修飾的變量來確保所有線程都能看到最新的標(biāo)志位值private volatile boolean flag= true;// 一個(gè)任務(wù)執(zhí)行線程,它持續(xù)檢查標(biāo)志位并根據(jù)標(biāo)志位決定是否繼續(xù)運(yùn)行public void runTask() {while (isRunning) { // 使用volatile修飾的變量確保我們能正確讀取到最新的狀態(tài)// 這里模擬一些耗時(shí)任務(wù)System.out.println("執(zhí)行任務(wù)...");try {Thread.sleep(1000); // 模擬耗時(shí)操作,這里暫停一秒} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("任務(wù)執(zhí)行線程已經(jīng)停止.");}public static void main(String[] args) throws InterruptedException {StopThreadExample example = new StopThreadExample();Thread taskThread = new Thread(() -> example.runTask()); // 啟動(dòng)任務(wù)執(zhí)行線程taskThread.start(); // 開始執(zhí)行任務(wù)線程try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}; // 讓主線程暫停幾秒以便觀察效果,模擬主線程正在做其他事情的情況System.out.println("停止任務(wù)執(zhí)行線程..."); // 在主線程中通知任務(wù)執(zhí)行線程停止執(zhí)行example.flag= false; // 設(shè)置標(biāo)志位來停止任務(wù)執(zhí)行線程,所有線程都會(huì)看到最新的標(biāo)志位值,因?yàn)槭褂昧藇olatile關(guān)鍵字修飾isRunning變量taskThread.join(); // 確保任務(wù)執(zhí)行線程停止后主線程繼續(xù)執(zhí)行接下來的代碼或結(jié)束程序(這一步依賴于主線程的下一步動(dòng)作)System.out.println("主線程結(jié)束."); // 當(dāng)任務(wù)執(zhí)行線程已經(jīng)停止后,主線程結(jié)束程序或繼續(xù)其他操作(這里模擬主線程的下一步動(dòng)作)}
}
在字節(jié)碼層面 volatile 原理,是通過 flags 表示來實(shí)現(xiàn)的
3.2 synchronized 是什么
synchronized
是 Java 中用于控制多線程并發(fā)訪問共享資源的一種關(guān)鍵字。它可以確保多個(gè)線程在同一時(shí)刻只能有一個(gè)線程執(zhí)行某個(gè)代碼塊或方法,從而避免并發(fā)問題,如競(jìng)態(tài)條件。以下是關(guān)于 synchronized
的詳細(xì)介紹:
3.2.1. 基本用法
3.2.1.1 同步方法
直接在方法聲明上使用 synchronized
關(guān)鍵字,這樣整個(gè)方法都在同步塊內(nèi)。例如:
public synchronized void synchronizedMethod() {// 同步代碼塊
}
此時(shí),該方法只能由一個(gè)線程在任何時(shí)候訪問。多個(gè)線程調(diào)用此方法時(shí),其他線程會(huì)被阻塞直到當(dāng)前線程執(zhí)行完畢。這種方法的同步鎖是當(dāng)前實(shí)例對(duì)象(即 this
)。如果要同步的是靜態(tài)方法,鎖是類的 Class
對(duì)象。
3.2.1.2 同步代碼塊
可以使用 synchronized(鎖對(duì)象)
語法創(chuàng)建一個(gè)同步代碼塊。例如:
synchronized (lockObject) {// 同步代碼塊內(nèi)容
}
此時(shí),只有持有鎖對(duì)象的線程可以進(jìn)入該代碼塊。多個(gè)線程可以嘗試訪問這個(gè)同步塊,但只有一個(gè)能獲得鎖并執(zhí)行其中的代碼。其他線程會(huì)被阻塞直到鎖被釋放。鎖對(duì)象可以是任何對(duì)象,通常是一個(gè)自然存在的共享資源或者一個(gè)特定的鎖對(duì)象。當(dāng)鎖對(duì)象是 this
時(shí),相當(dāng)于同步整個(gè)方法。如果鎖對(duì)象是某個(gè)靜態(tài)變量,那么任何線程調(diào)用這段代碼都需要獲得那個(gè)鎖才能執(zhí)行。但一定要確保使用的鎖對(duì)象不會(huì)在方法中頻繁變化。如果對(duì)象在不同的上下文中表示不同的資源或意義,則可能導(dǎo)致意外的并發(fā)問題。
3.2.2. 工作原理
當(dāng)一個(gè)線程進(jìn)入一個(gè) synchronized
代碼塊或方法時(shí),它首先嘗試獲取鎖對(duì)象對(duì)應(yīng)的內(nèi)置鎖(也稱為監(jiān)視器鎖)。如果鎖已經(jīng)被另一個(gè)線程持有,那么該線程將會(huì)進(jìn)入等待狀態(tài),直到鎖被釋放(由持有鎖的線程退出同步塊或調(diào)用 wait()
方法)。這種機(jī)制確保了同一時(shí)刻只有一個(gè)線程可以訪問同步代碼塊或方法中的共享資源。一旦線程釋放鎖(退出同步塊或方法),其他等待的線程將有機(jī)會(huì)嘗試獲取該鎖并執(zhí)行相應(yīng)的代碼。如果多個(gè)線程同時(shí)嘗試獲取同一個(gè)對(duì)象的鎖,它們會(huì)被按照某種順序來串行化地執(zhí)行同步代碼塊或方法。值得注意的是,同步控制開銷通常比上下文切換的開銷要小得多。此外,JVM 會(huì)嘗試優(yōu)化鎖的獲取和釋放過程以提高性能。對(duì)于復(fù)雜的應(yīng)用場(chǎng)景,還有專門的性能調(diào)優(yōu)和死鎖避免策略可用。
3.2.3. 保證原子性和可見性
是的,我的解釋是正確的。在 Java 中使用 synchronized
關(guān)鍵字時(shí),確實(shí)涉及到內(nèi)存模型和工作內(nèi)存與主內(nèi)存的刷新問題,特別是在多線程環(huán)境下。讓我們?cè)俅卧敿?xì)解釋這一點(diǎn)。
當(dāng)你在 Java 程序中使用 synchronized
塊時(shí),這個(gè)關(guān)鍵字確保了多個(gè)線程對(duì)共享資源的同步訪問。這是通過確保只有一個(gè)線程能夠獲取到鎖來實(shí)現(xiàn)的。在這個(gè)同步塊內(nèi),所有的讀寫操作都是對(duì)該鎖對(duì)象的原子操作。這就意味著當(dāng)一個(gè)線程進(jìn)入 synchronized
塊時(shí),它會(huì)獲取鎖對(duì)象,并開始執(zhí)行同步塊內(nèi)的代碼。此時(shí),其他嘗試進(jìn)入該同步塊的線程會(huì)被阻塞,直到持有鎖的線程釋放鎖為止。
在這個(gè)過程中,涉及到內(nèi)存模型和工作內(nèi)存與主內(nèi)存的交互:
- 互斥訪問:當(dāng)一個(gè)線程進(jìn)入 synchronized 塊時(shí),它需要獲得鎖。只有獲得鎖的線程才能執(zhí)行同步塊內(nèi)的代碼,從而保證了同一時(shí)刻只有一個(gè)線程在操作共享變量。
- 工作內(nèi)存與主內(nèi)存的刷新:當(dāng)一個(gè)線程持有鎖并執(zhí)行同步塊內(nèi)的操作時(shí),它會(huì)首先從主內(nèi)存中讀取共享資源到工作內(nèi)存中(即線程的本地緩存)。在工作內(nèi)存中修改這些數(shù)據(jù)后,只有當(dāng)線程釋放鎖并允許其他線程獲取鎖時(shí),這些修改才會(huì)被寫回主內(nèi)存。這就意味著同步塊內(nèi)的操作保證了在工作內(nèi)存中對(duì)共享數(shù)據(jù)的操作在所有線程之間是一致的。
- 緩存一致性:在多線程環(huán)境中,緩存一致性是確保多個(gè)線程之間共享的數(shù)據(jù)保持一致性的過程。
synchronized
塊確保了在任何時(shí)候只有一個(gè)線程能夠修改共享數(shù)據(jù),從而避免了緩存不一致的問題。即使其他線程嘗試讀取或?qū)懭牍蚕頂?shù)據(jù),由于它們被阻塞在鎖之外,它們不能看到在工作內(nèi)存中的修改,除非持有鎖的線程釋放鎖并允許它們獲取鎖。
簡(jiǎn)而言之,synchronized
通過確保對(duì)共享資源的獨(dú)占訪問來強(qiáng)制工作內(nèi)存與主內(nèi)存的刷新,從而保證了緩存一致性。它確保了任何線程在訪問或修改共享數(shù)據(jù)時(shí)都是實(shí)時(shí)的并且一致的。
3.3 synchronized 原理和例子
public class Counter {private int count = 0; // 共享資源:計(jì)數(shù)器// synchronized 塊用于保護(hù)對(duì) count 的并發(fā)訪問public synchronized void increment() {count++; // 對(duì)共享資源的操作(這里是增加計(jì)數(shù)器的值)}public int getCount() {return count; // 外部訪問共享資源(可能需要同步)}public static void main(String[] args) throws InterruptedException {Counter counter = new Counter(); // 創(chuàng)建 Counter 實(shí)例對(duì)象// 創(chuàng)建多個(gè)線程,模擬并發(fā)增加計(jì)數(shù)器的值for (int i = 0; i < 10; i++) { // 創(chuàng)建 10 個(gè)線程來并發(fā)增加計(jì)數(shù)器的值Thread thread = new Thread(() -> {for (int j = 0; j < 100; j++) { // 每個(gè)線程都嘗試增加計(jì)數(shù)器值多次以模擬并發(fā)情況counter.increment(); // 使用 synchronized 塊來安全地增加計(jì)數(shù)器值}});thread.start(); // 啟動(dòng)線程thread.join();} // 循環(huán)結(jié)束后,所有線程都已經(jīng)啟動(dòng)并嘗試增加計(jì)數(shù)器的值。由于使用了 synchronized 塊,計(jì)數(shù)器的值將準(zhǔn)確累積而不會(huì)發(fā)生沖突或重疊增加的情況。可以檢查 getCount() 的返回值來驗(yàn)證這一點(diǎn)。System.out.println(counter.count);}
}
在字節(jié)碼層面 synchronized 原理
-
方法同步: 對(duì)于使用
synchronized
修飾的方法,JVM會(huì)在方法的訪問修飾符字節(jié)碼指令之前插入一個(gè)特殊的指令,稱為“ACC_SYNCHRONIZED”。這個(gè)標(biāo)志告訴JVM該方法需要一個(gè)鎖來執(zhí)行。當(dāng)線程調(diào)用這個(gè)方法時(shí),它必須先獲得對(duì)象的內(nèi)置鎖(即實(shí)例鎖)。持有鎖的線程可以在該對(duì)象的任何方法上執(zhí)行任何操作,而不會(huì)阻塞其他線程對(duì)同一個(gè)對(duì)象的訪問。當(dāng)方法返回時(shí),鎖會(huì)被自動(dòng)釋放。 -
代碼塊同步: 對(duì)于使用
synchronized(obj)
修飾的代碼塊,Java編譯器會(huì)在該代碼塊的起始和結(jié)束位置生成特定的字節(jié)碼指令。具體來說,它會(huì)使用monitorenter
和monitorexit
指令。monitorenter
指令用于獲取對(duì)象的內(nèi)部鎖,而monitorexit
指令用于釋放該鎖。這兩個(gè)指令確保了同步代碼塊中的代碼是原子的,并且不會(huì)被其他嘗試獲取同一鎖的線程中斷。如果在持有鎖的過程中發(fā)生了異常,需要確保monitorexit
指令始終被執(zhí)行以釋放鎖。
四、總結(jié)
在多線程編程中,線程之間的通信非常重要,它可以實(shí)現(xiàn)資源共享、任務(wù)分工、同步操作、數(shù)據(jù)傳遞和提高性能等功能。為了確保線程之間的通信的正確性和效率,可以使用volatile關(guān)鍵字和synchronized關(guān)鍵字。volatile關(guān)鍵字可以保證可見性和禁止指令重排序優(yōu)化,而synchronized關(guān)鍵字可以保證對(duì)共享資源的同步訪問,保證原子性和可見性。