摘要:所以多線程條件下使用關(guān)鍵字的前提是對(duì)變量的寫操作不依賴于變量的當(dāng)前值,而賦值操作很明顯滿足這一前提。在多線程環(huán)境下,正確使用關(guān)鍵字可以比直接使用更加高效而且代碼簡(jiǎn)潔,但是使用關(guān)鍵字也更容易出錯(cuò)。
volatile 作為 Java 語(yǔ)言的一個(gè)關(guān)鍵字,被看作是輕量級(jí)的 synchronized(鎖)。雖然 volatile 只具有synchronized 的部分功能,但是一般使用 volatile 會(huì)比使用 synchronized 更有效率。在編寫多線程程序的時(shí)候,volatile 修飾的變量能夠:
保證內(nèi)存 可見(jiàn)性
防止指令 重排序
保證對(duì) 64 位變量 讀寫的原子性
一. 保證內(nèi)存可見(jiàn)性JVM 中,每個(gè)線程都擁有自己棧內(nèi)存,用來(lái)保存當(dāng)前線程運(yùn)行過(guò)程中的變量數(shù)據(jù);然后多個(gè)線程之間共享堆內(nèi)存(也稱主存)。當(dāng)線程需要訪問(wèn)一個(gè)變量時(shí),首先將其從堆內(nèi)存中復(fù)制到自己的棧內(nèi)存作為副本,然后線程每次對(duì)該變量的操作,都將是對(duì)棧中的副本進(jìn)行操作 —— 在某些時(shí)刻(比如退出 synchronized 塊或線程結(jié)束),線程會(huì)將棧中副本的值寫回到主存,此時(shí)主存中的變量才會(huì)被替換為副本的值。這樣自然就帶來(lái)一個(gè)問(wèn)題,即如果兩個(gè)線程共享一個(gè)變量,線程A 改變了變量的值,但是 線程B 可能無(wú)法立即發(fā)現(xiàn)。比如下面這個(gè)經(jīng)典的例子:
public class ConcurrentTest { private static boolean running = true; public static class AnotherThread extends Thread { @Override public void run() { System.out.println("AnotherThread is running"); while (running) { } System.out.println("AnotherThread is stoped"); } } public static void main(String[] args) throws Exception { new AnotherThread ().start(); Thread.sleep(1000); running = false; // 1 秒之后想停止 AnotherThread } }
上面這段代碼一般情況下都會(huì)死鎖,就是因?yàn)樵?main 方法(主線程)中對(duì) running 做的修改,并不能立馬對(duì) AnotherThread 可見(jiàn)。
如果將 running 加上修飾符 volatile,那么便可以獲取實(shí)際希望的結(jié)果,因?yàn)榇藭r(shí)主線程中設(shè)置 running 為 false 之后,AnotherThread 可以立馬發(fā)現(xiàn) running 的值發(fā)生了改變:
二. 防止指令重排序對(duì)于 volatile 修飾的變量,JVM 可以保證:
每次對(duì)該變量的寫操作,都將立即同步到主存;
每次對(duì)該變量的讀操作,都將從主存讀取,而不是線程棧
如果一個(gè)操作不是原子操作,那么 JVM 便可能會(huì)對(duì)該操作涉及的指令進(jìn)行 重排序。重排序即在不改變程序語(yǔ)義的前提下,通過(guò)調(diào)整指令的執(zhí)行順序,盡可能達(dá)到提高運(yùn)行效率的目的。
對(duì)于單例模式,為了達(dá)到延時(shí)初始化,并且可以在多線程環(huán)境下使用,我們可以直接使用 synchronized 關(guān)鍵字:
public class Singleton { public static Singleton instance = null; private Singleton() { } public synchronized static Singleton getSingleton() { if (instance == null) { instance = new Singleton(); } return instance; } }
這樣做的缺陷也很明顯,那就是 instance 初始化完畢之后,以后每次獲取 instance 仍然需要進(jìn)行加鎖操作,是個(gè)很大的效率浪費(fèi)。
于是出現(xiàn)了一種經(jīng)典寫法叫 “雙重檢測(cè)鎖”:
public class Singleton { public static Singleton instance = null; private Singleton() { } public static Singleton getSingleton() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
但是這樣的寫法同樣會(huì)存在問(wèn)題,因?yàn)?instance = new Singleton() 并非原子操作,其大概可以等同于執(zhí)行:
分配一個(gè) Singleton 對(duì)應(yīng)的內(nèi)存
初始化這個(gè) Singleton 對(duì)應(yīng)的內(nèi)存
將 instance 指向?qū)?yīng)的內(nèi)存的地址
其中,2 依賴于 1,但是 3 并不依賴于 2 —— 所以,存在 JVM 將這三條語(yǔ)句重排序?yàn)?1->3->2 的可能,即變?yōu)椋?/p>
a. 分配一個(gè) Singleton 對(duì)應(yīng)的內(nèi)存
b. 將 instance 指向?qū)?yīng)的內(nèi)存的地址
c. 初始化這個(gè) Singleton 對(duì)應(yīng)的內(nèi)存
此時(shí)如果 線程A 執(zhí)行完 b,那么此時(shí)的 instance 指向的內(nèi)存并不為 null,然而這塊內(nèi)存卻還沒(méi)有被初始化。當(dāng) 線程B 此時(shí)判斷第一個(gè) if (instance == null) 時(shí)發(fā)現(xiàn) instance 并不為 null,便會(huì)將此時(shí)的 instance 返回 —— 但 Singleton 的初始化可能并未完成,此時(shí) 線程B 使用 instance 便可能會(huì)出現(xiàn)錯(cuò)誤。
在 JDK 1.5 之后,增強(qiáng)了 volatile 的語(yǔ)義,嚴(yán)格限制 JVM (編譯器、處理器)不能對(duì) volatile 修飾的變量涉及的操作指令進(jìn)行重排序。
所以為了避免對(duì) instance 變量涉及的操作進(jìn)行重排序,保證 “雙重檢測(cè)鎖” 的正確性,我們可以將 instance 使用 volatile 修飾:
public class Singleton { /* 使用 volatile 修飾 */ public static volatile Singleton instance = null; private Singleton() { } public static Singleton getSingleton() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }三. 保證對(duì) 64 位變量讀寫的原子性
JVM 可以保證對(duì) 32位 數(shù)據(jù)讀寫的原子性,但是對(duì)于 long 和 double 這樣 64位 的數(shù)據(jù)的讀寫,會(huì)將其分為 高32位 和 低32位 分兩次讀寫。所以對(duì)于long 或 double 的讀寫并不是原子性的,這樣在并發(fā)程序中共享 long 或 double 變量就可能會(huì)出現(xiàn)問(wèn)題,于是 JVM 提供了 volatile 關(guān)鍵字來(lái)解決這個(gè)問(wèn)題:
使用 volatile 修飾的 long 或 double 變量,JVM 可以保證對(duì)其讀寫的原子性。
但值得注意的是,此處的 “寫” 僅指對(duì) 64位 的變量進(jìn)行直接賦值。而對(duì)于 i++ 這個(gè)語(yǔ)句,事實(shí)上涉及了 讀取-修改-寫入 三個(gè)操作:
讀取變量到棧中某個(gè)位置
對(duì)棧中該位置的值進(jìn)行自增
將自增后的值寫回到變量對(duì)應(yīng)的存儲(chǔ)位置
因此哪怕變量 i 使用 volatile 修飾,也并不能使涉及上面三個(gè)操作的 i++ 具有原子性。所以多線程條件下使用 volatile 關(guān)鍵字的前提是:對(duì)變量的寫操作不依賴于變量的當(dāng)前值,而賦值操作很明顯滿足這一前提。
在多線程環(huán)境下,正確使用 volatile 關(guān)鍵字可以比直接使用 synchronized 更加高效而且代碼簡(jiǎn)潔,但是使用 volatile 關(guān)鍵字也更容易出錯(cuò)。所以,除非十分清楚 volatile 的使用場(chǎng)景,否則還是應(yīng)該選擇更加具有保障性的 synchronized。
Brian Goetz 大大寫過(guò)一篇 “volatile 變量使用指南”,有興趣的讀者可以參閱:Java 理論與實(shí)踐: 正確使用 Volatile 變量
volatile 變量的底層實(shí)現(xiàn)原理,有興趣的讀者可以參閱:
http://www.infoq.com/cn/artic...
http://www.cnblogs.com/paddix...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/66780.html
摘要:三關(guān)鍵字能保證原子性嗎并發(fā)編程藝術(shù)這本書上說(shuō)保證但是在自增操作非原子操作上不保證,多線程編程核心藝術(shù)這本書說(shuō)不保證。多線程訪問(wèn)關(guān)鍵字不會(huì)發(fā)生阻塞,而關(guān)鍵字可能會(huì)發(fā)生阻塞關(guān)鍵字能保證數(shù)據(jù)的可見(jiàn)性,但不能保證數(shù)據(jù)的原子性。 系列文章傳送門: Java多線程學(xué)習(xí)(一)Java多線程入門 Java多線程學(xué)習(xí)(二)synchronized關(guān)鍵字(1) java多線程學(xué)習(xí)(二)synchroniz...
時(shí)間:2017年07月09日星期日說(shuō)明:本文部分內(nèi)容均來(lái)自慕課網(wǎng)。@慕課網(wǎng):http://www.imooc.com教學(xué)源碼:無(wú)學(xué)習(xí)源碼:https://github.com/zccodere/s... 第一章:課程簡(jiǎn)介 1-1 課程簡(jiǎn)介 課程目標(biāo)和學(xué)習(xí)內(nèi)容 共享變量在線程間的可見(jiàn)性 synchronized實(shí)現(xiàn)可見(jiàn)性 volatile實(shí)現(xiàn)可見(jiàn)性 指令重排序 as-if-seria...
摘要:的缺點(diǎn)頻繁刷新主內(nèi)存中變量,可能會(huì)造成性能瓶頸不具備操作的原子性,不適合在對(duì)該變量的寫操作依賴于變量本身自己。 作者:畢來(lái)生微信:878799579 1. 什么是JUC? JUC全稱 java.util.concurrent 是在并發(fā)編程中很常用的實(shí)用工具類 2.Volatile關(guān)鍵字 1、如果一個(gè)變量被volatile關(guān)鍵字修飾,那么這個(gè)變量對(duì)所有線程都是可見(jiàn)的。2、如果某條線程修...
摘要:本文從內(nèi)存模型角度,探討的實(shí)現(xiàn)原理。通過(guò)共享內(nèi)存或者消息通知這兩種方法,可以實(shí)現(xiàn)通信或同步?;诠蚕韮?nèi)存的線程通信是隱式的,線程同步是顯式的而基于消息通知的線程通信是顯式的,線程同步是隱式的。鎖規(guī)則鎖的解鎖,于于鎖的獲取或加鎖。 一、前言 在java多線程編程中,volatile可以用來(lái)定義輕量級(jí)的共享變量,它比synchronized的使用成本更低,因?yàn)樗粫?huì)引起線程上下文的切換和調(diào)...
摘要:今天給大家總結(jié)一下,面試中出鏡率很高的幾個(gè)多線程面試題,希望對(duì)大家學(xué)習(xí)和面試都能有所幫助。指令重排在單線程環(huán)境下不會(huì)出先問(wèn)題,但是在多線程環(huán)境下會(huì)導(dǎo)致一個(gè)線程獲得還沒(méi)有初始化的實(shí)例。使用可以禁止的指令重排,保證在多線程環(huán)境下也能正常運(yùn)行。 下面最近發(fā)的一些并發(fā)編程的文章匯總,通過(guò)閱讀這些文章大家再看大廠面試中的并發(fā)編程問(wèn)題就沒(méi)有那么頭疼了。今天給大家總結(jié)一下,面試中出鏡率很高的幾個(gè)多線...
閱讀 2439·2021-09-30 09:47
閱讀 3006·2019-08-30 11:05
閱讀 2600·2019-08-29 17:20
閱讀 1985·2019-08-29 13:01
閱讀 1787·2019-08-26 13:39
閱讀 1375·2019-08-26 13:26
閱讀 3283·2019-08-23 18:40
閱讀 1925·2019-08-23 17:09