java多線程編程必備volatile與synchronized深入理解
Volatile概述
Volatile是Java中的一種輕量級(jí)同步機(jī)制,用于保證變量的可見性和禁止指令重排。當(dāng)一個(gè)變量被聲明為Volatile類型時(shí),任何修改該變量的操作都會(huì)立即被所有線程看到。也就是說,Volatile修飾的變量在每次修改時(shí)都會(huì)強(qiáng)制將修改刷新到主內(nèi)存中,具有很好的可見性和線程安全性。
上圖可以看到在多線程編程中,線程沒有直接操作主內(nèi)存,而是把主內(nèi)存中的數(shù)據(jù)拷貝到工作內(nèi)存中也就是共享變量副本的方式操作變量。當(dāng)一個(gè)變量被多個(gè)線程共享時(shí),如果其中一個(gè)線程修改了這個(gè)變量的值,另一個(gè)線程在讀取這個(gè)變量時(shí)可能會(huì)得到過期的值。這是因?yàn)榫€程之間存在緩存不一致的問題。
使用 volatile 關(guān)鍵字可以解決這個(gè)問題。volatile 保證了對(duì)該變量的訪問都是直接針對(duì)主內(nèi)存中的變量進(jìn)行的,而不是訪問線程本地的緩存。因此,當(dāng)一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看到這個(gè)變化,避免了緩存不一致的問題。
具體實(shí)現(xiàn)原理是,在使用 volatile 關(guān)鍵字修飾的變量進(jìn)行讀寫操作時(shí),會(huì)禁止 CPU 的緩存優(yōu)化,每次操作都要直接讀寫主內(nèi)存。同時(shí),在進(jìn)行讀操作時(shí),也會(huì)強(qiáng)制從主內(nèi)存中讀取最新的值,而不是使用線程本地的緩存。這樣就可以保證多線程之間的變量訪問是同步、可見的。
Synchronized概述
Synchronized是Java中的一種重量級(jí)同步機(jī)制,用于保證線程安全和排除數(shù)據(jù)競(jìng)爭(zhēng)。當(dāng)一個(gè)方法被聲明為Synchronized時(shí),同一時(shí)間只有一個(gè)線程可以訪問該方法,其他線程必須等待。這樣可以避免多個(gè)線程同時(shí)訪問共享資源導(dǎo)致數(shù)據(jù)不一致的問題。
Volatile與Synchronized的區(qū)別
(1)Volatile是一種輕量級(jí)的同步機(jī)制,Synchronized是一種重量級(jí)的同步機(jī)制。
(2)Volatile用于保證變量的可見性和禁止指令重排,Synchronized用于排除數(shù)據(jù)競(jìng)爭(zhēng)和保證線程安全。
(3)Volatile不能保證變量的原子性,Synchronized可以保證同步代碼塊的原子性。
(4)Volatile的性能遠(yuǎn)高于Synchronized,但只適用于變量的情況,而Synchronized則適用于任意類型的對(duì)象或代碼塊。
(5)volatile修飾變量,僅用于變量級(jí)(不會(huì)造成線程阻塞),線程讀寫時(shí)均刷新內(nèi)存,只保證可見性。volatile還可以禁止指令重排。
(6)synchronized鎖變量或代碼段,鎖級(jí)(會(huì)造成線程阻塞),能保證可見性與原子性
使用場(chǎng)景
1 Volatile的使用場(chǎng)景
由于Volatile的可見性和禁止指令重排的特點(diǎn),其在一些特定的場(chǎng)景下非常有用,例如:
(1)用于控制線程的開關(guān)、狀態(tài)標(biāo)志、計(jì)數(shù)器等變量;
(2)用于發(fā)布一些不變的對(duì)象,例如單例模式中的實(shí)例;
(3)用于性能調(diào)優(yōu),避免鎖競(jìng)爭(zhēng),例如CAS算法等。
以下是使用Java編寫一個(gè)演示volatile關(guān)鍵字的簡單示例:
public class VolatileDemo { private volatile boolean isRunning = true; public void start() { System.out.println("Starting the thread..."); new Thread(() -> { while (isRunning) { // do some work here } System.out.println("Thread stopped."); }).start(); } public void stop() { System.out.println("Stopping the thread..."); isRunning = false; } public static void main(String[] args) throws InterruptedException { VolatileDemo demo = new VolatileDemo(); demo.start(); // wait for 3 seconds Thread.sleep(3000); demo.stop(); } }
在上述示例中,我們創(chuàng)建了一個(gè)名為VolatileDemo的類,并聲明了一個(gè)volatile變量isRunning。此變量用于指示線程是否應(yīng)該繼續(xù)運(yùn)行。在start()方法中,我們啟動(dòng)了一個(gè)新線程,并在while循環(huán)中檢查isRunning變量的值。由于isRunning變量是volatile類型的,因此在不同的線程之間更改它的值時(shí),該值將始終保持同步。在stop()方法中,我們將isRunning變量的值設(shè)置為false,以便使線程停止運(yùn)行。在main()方法中,我們創(chuàng)建了一個(gè)VolatileDemo對(duì)象并調(diào)用start()方法來開始線程的執(zhí)行。然后,我們等待3秒鐘,隨后調(diào)用stop()方法來停止線程的執(zhí)行。由于isRunning變量是volatile類型的,因此線程能夠及時(shí)地檢測(cè)到isRunning變量的更改,從而停止線程的執(zhí)行。
2 Synchronized的使用場(chǎng)景
Synchronized機(jī)制由于其強(qiáng)制性的同步性,在保證線程安全和數(shù)據(jù)完整性的同時(shí),也會(huì)帶來一些性能上的開銷。因此,在使用Synchronized時(shí)需要控制同步的范圍和頻率,適當(dāng)使用Synchronized可以提高程序的效率和可靠性。使用Synchronized的場(chǎng)景包括:
(1)對(duì)共享變量的訪問和修改,例如在多線程情況下對(duì)數(shù)據(jù)進(jìn)行同步處理;
(2)對(duì)類實(shí)例化的構(gòu)造函數(shù)進(jìn)行同步,確保實(shí)例化過程中的線程安全;
(3)對(duì)靜態(tài)變量和方法的訪問和修改,例如在多線程情況下對(duì)靜態(tài)變量進(jìn)行同步。
以下是使用Java編寫一個(gè)演示synchronized關(guān)鍵字的簡單示例:
public class SynchronizedDemo { private int counter = 0; public synchronized void increment() { counter++; } public synchronized int getCounter() { return counter; } public static void main(String[] args) throws InterruptedException { SynchronizedDemo demo = new SynchronizedDemo(); // create multiple threads to increment counter for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { demo.increment(); } }).start(); } // wait for all threads to finish Thread.sleep(5000); // print the final value of counter System.out.println("Final value of counter: " + demo.getCounter()); } }
在上述示例中,我們創(chuàng)建了一個(gè)名為SynchronizedDemo的類,并聲明了一個(gè)私有變量counter。我們?cè)趇ncrement()方法和getCounter()方法上加了synchronized關(guān)鍵字來確保每次只有一個(gè)線程可以訪問這些方法。
在main()方法中,我們創(chuàng)建了10個(gè)線程來并發(fā)地執(zhí)行increment()方法,每個(gè)線程將計(jì)數(shù)器增加1000次。由于increment()方法是同步的,因此在任何時(shí)候都只有一個(gè)線程可以訪問它,從而避免了數(shù)據(jù)競(jìng)爭(zhēng)和不一致性。
最后,我們等待所有線程完成后打印計(jì)數(shù)器的最終值。由于getCounter()方法也是同步的,所以它返回的值將始終是最新的和正確的。
注意事項(xiàng)
(1)使用Volatile時(shí)需要注意可見性和原子性,不能保證多個(gè)操作的原子性。
(2)使用Synchronized時(shí)需要注意同步塊的范圍和對(duì)象的鎖定,避免死鎖和性能問題。
(3)在多線程編程中,需要根據(jù)實(shí)際情況選擇合適的同步機(jī)制或使用多種同步機(jī)制的組合,以保證程序的正確性和可靠性。
相關(guān)面試問題
- volatile 和 synchronized 有什么區(qū)別?
答:volatile 關(guān)鍵字只能保證變量的可見性,不能保證原子性以及同步性,而 synchronized 關(guān)鍵字既能保證原子性和同步性,也能保證變量的可見性。
- volatile 能否替代 synchronized?
答:不能。雖然 volatile 可以保證變量的可見性,但是它無法保證原子性和同步性,而 synchronized 能夠保證這三種特性。
- synchronized 與 volatile 的實(shí)現(xiàn)機(jī)制有何不同?
答:synchronized 是通過對(duì)對(duì)象或類進(jìn)行加鎖來保證同步性和原子性的,其效率相對(duì)較低;而 volatile 是通過禁止 CPU 緩存優(yōu)化來保證變量的可見性,其開銷相對(duì)較小,但不能保證同步性和原子性。
- 在什么情況下需要使用 volatile?
答:當(dāng)一個(gè)變量被多個(gè)線程訪問時(shí),可能存在緩存不一致的問題,此時(shí)需要使用 volatile 來保證變量的可見性。
- synchronized 和 volatile 的適用場(chǎng)景分別是什么?
答:synchronized 適用于保證臨界區(qū)代碼的原子性和同步性,而 volatile 適用于保證變量的可見性。因此,如果需要進(jìn)行復(fù)雜的操作并且需要保證原子性和同步性時(shí),應(yīng)該使用 synchronized;而如果只需要保證變量的可見性時(shí),可以使用 volatile。
以上就是java多線程編程必備volatile與synchronized深入理解的詳細(xì)內(nèi)容,更多關(guān)于java多線程volatile synchronized的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Java中的有參構(gòu)造方法與無參構(gòu)造方法
這篇文章主要詳細(xì)介紹了Java中有參構(gòu)造方法與無參構(gòu)造方法,文中有詳細(xì)的代碼示例,讓大家清晰明了的了解到有參構(gòu)造方法與無參構(gòu)造方法、以及應(yīng)用,需要的朋友可以參考下2023-06-06SpringBoot集成mybatis連接oracle的圖文教程
這篇文章主要介紹了Spring Boot集成mybatis連接oracle的圖文教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02java實(shí)現(xiàn)把對(duì)象數(shù)組通過excel方式導(dǎo)出的功能
本文主要介紹了java實(shí)現(xiàn)把對(duì)象數(shù)組通過excel方式導(dǎo)出的功能的相關(guān)知識(shí)。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-03-03java中基本注解的知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家整理的是一篇關(guān)于java中基本注解的知識(shí)點(diǎn)總結(jié),有需要的朋友們可以跟著學(xué)習(xí)下。2021-06-06SpringCloud?hystrix斷路器與全局解耦全面介紹
什么是服務(wù)降級(jí)?當(dāng)服務(wù)器壓力劇增的情況下,根據(jù)實(shí)際業(yè)務(wù)情況及流量,對(duì)一些服務(wù)和頁面有策略的不處理或換種簡單的方式處理,從而釋放服務(wù)器資源以保證核心交易正常運(yùn)作或高效運(yùn)作2022-10-10