亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

Java 并發編程:volatile的使用及其原理

2019-11-14 11:04:36
字體:
來源:轉載
供稿:網友

一、volatile的作用

在《java并發編程:核心理論》一文中,我們已經提到過可見性、有序性及原子性問題,通常情況下我們可以通過Synchronized關鍵字來解決這些個問題,不過如果對Synchronized原理有了解的話,應該知道Synchronized是一個比較重量級的操作,對系統的性能有比較大的影響,所以,如果有其他解決方案,我們通常都避免使用Synchronized來解決問題。而volatile關鍵字就是Java中提供的另一種解決可見性和有序性問題的方案。對于原子性,需要強調一點,也是大家容易誤解的一點:對volatile變量的單次讀/寫操作可以保證原子性的,如long和double類型變量,但是并不能保證i++這種操作的原子性,因為本質上i++是讀、寫兩次操作。

二、volatile的使用

  關于volatile的使用,我們可以通過幾個例子來說明其使用方式和場景。

1、防止重排序

  我們從一個最經典的例子來分析重排序問題。大家應該都很熟悉單例模式的實現,而在并發環境下的單例實現方式,我們通常可以采用雙重檢查加鎖(DCL)的方式來實現。其源碼如下: “` package com.paddx.test.concurrent;

public class Singleton { public static volatile Singleton singleton;

/** * 構造函數私有,禁止外部實例化 */ PRivate Singleton() {};

public static Singleton getInstance() { if (singleton == null) { synchronized (singleton) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } “`

  現在我們分析一下為什么要在變量singleton之間加上volatile關鍵字。要理解這個問題,先要了解對象的構造過程,實例化一個對象其實可以分為三個步驟:

 ?。?)分配內存空間。

 ?。?)初始化對象。

 ?。?)將內存空間的地址賦值給對應的引用。

但是由于操作系統可以對指令進行重排序,所以上面的過程也可能會變成如下過程:

 ?。?)分配內存空間。

  (2)將內存空間的地址賦值給對應的引用。

  (3)初始化對象

  如果是這個流程,多線程環境下就可能將一個未初始化的對象引用暴露出來,從而導致不可預料的結果。因此,為了防止這個過程的重排序,我們需要將變量設置為volatile類型的變量。

2、實現可見性

  可見性問題主要指一個線程修改了共享變量值,而另一個線程卻看不到。引起可見性問題的主要原因是每個線程擁有自己的一個高速緩存區——線程工作內存。volatile關鍵字能有效的解決這個問題,我們看下下面的例子,就可以知道其作用:

“` package com.paddx.test.concurrent;

public class VolatileTest { int a = 1; int b = 2;

public void change(){ a = 3; b = a; }

public void print(){ System.out.println(“b=”+b+”;a=”+a); }

public static void main(String[] args) { while (true){ final VolatileTest test = new VolatileTest(); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } test.change(); } }).start();

new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } test.print(); } }).start(); }

} } “`

  直觀上說,這段代碼的結果只可能有兩種:b=3;a=3 或 b=2;a=1。不過運行上面的代碼(可能時間上要長一點),你會發現除了上兩種結果之外,還出現了第三種結果:

…… b=2;a=1 b=2;a=1 b=3;a=3 b=3;a=3 b=3;a=1 b=3;a=3 b=2;a=1 b=3;a=3 b=3;a=3 ……

  為什么會出現b=3;a=1這種結果呢?正常情況下,如果先執行change方法,再執行print方法,輸出結果應該為b=3;a=3。相反,如果先執行的print方法,再執行change方法,結果應該是 b=2;a=1。那b=3;a=1的結果是怎么出來的?原因就是第一個線程將值a=3修改后,但是對第二個線程是不可見的,所以才出現這一結果。如果將a和b都改成volatile類型的變量再執行,則再也不會出現b=3;a=1的結果了。

3、保證原子性

  關于原子性的問題,上面已經解釋過。volatile只能保證對單次讀/寫的原子性。這個問題可以看下JLS中的描述:

“` 17.7 Non-Atomic Treatment of double and long

For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.

Writes and reads of volatile long and double values are always atomic.

Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.

Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32-bit values. For efficiency’s sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts.

Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications. “`

  這段話的內容跟我前面的描述內容大致類似。因為long和double兩種數據類型的操作可分為高32位和低32位兩部分,因此普通的long或double類型讀/寫可能不是原子的。因此,鼓勵大家將共享的long和double變量設置為volatile類型,這樣能保證任何情況下對long和double的單次讀/寫操作都具有原子性。

  關于volatile變量對原子性保證,有一個問題容易被誤解?,F在我們就通過下列程序來演示一下這個問題:

“` package com.paddx.test.concurrent;

public class VolatileTest01 { volatile int i;

public void addI(){ i++; }

public static void main(String[] args) throws InterruptedException { final VolatileTest01 test01 = new VolatileTest01(); for (int n = 0; n < 1000; n++) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } test01.addI(); } }).start(); }

Thread.sleep(10000);//等待10秒,保證上面程序執行完成 System.out.println(test01.i);

} } “`

大家可能會誤認為對變量i加上關鍵字volatile后,這段程序就是線程安全的。大家可以嘗試運行上面的程序。下面是我本地運行的結果: 這里寫圖片描述   可能每個人運行的結果不相同。不過應該能看出,volatile是無法保證原子性的(否則結果應該是1000)。原因也很簡單,i++其實是一個復合操作,包括三步驟:

 ?。?)讀取i的值。

 ?。?)對i加1。

 ?。?)將i的值寫回內存。

volatile是無法保證這三個操作是具有原子性的,我們可以通過AtomicInteger或者Synchronized來保證+1操作的原子性。

注:上面幾段代碼中多處執行了Thread.sleep()方法,目的是為了增加并發問題的產生幾率,無其他作用。

三、volatile的原理

  通過上面的例子,我們基本應該知道了volatile是什么以及怎么使用?,F在我們再來看看volatile的底層是怎么實現的。

1、可見性實現:

  在前文中已經提及過,線程本身并不直接與主內存進行數據的交互,而是通過線程的工作內存來完成相應的操作。這也是導致線程間數據不可見的本質原因。因此要實現volatile變量的可見性,直接從這方面入手即可。對volatile變量的寫操作與普通變量的主要區別有兩點:

  (1)修改volatile變量時會強制將修改后的值刷新的主內存中。

 ?。?)修改volatile變量后會導致其他線程工作內存中對應的變量值失效。因此,再讀取該變量值的時候就需要重新從讀取主內存中的值。

  通過這兩個操作,就可以解決volatile變量的可見性問題。

2、有序性實現:

  在解釋這個問題前,我們先來了解一下Java中的happen-before規則,JSR 133中對Happen-before的定義如下:

Two actions can be ordered by a happens-before relationship.If one action happens before another, then the first is visible to and ordered before the second.

  通俗一點說就是如果a happen-before b,則a所做的任何操作對b是可見的。(這一點大家務必記住,因為happen-before這個詞容易被誤解為是時間的前后)。我們再來看看JSR 133中定義了哪些happen-before規則:

? Each action in a thread happens before every subsequent action in that thread. ? An unlock on a monitor happens before every subsequent lock on that monitor. ? A write to a volatile field happens before every subsequent read of that volatile. ? A call to start() on a thread happens before any actions in the started thread. ? All actions in a thread happen before any other thread successfully returns from a join() on that thread. ? If an action a happens before an action b, and b happens before an action c, then a happens before c.

  翻譯過來為:   這里寫圖片描述

規則表: 這里寫圖片描述

3、內存屏障

  為了實現volatile可見性和happen-befor的語義。JVM底層是通過一個叫做“內存屏障”的東西來完成。內存屏障,也叫做內存柵欄,是一組處理器指令,用于實現對內存操作的順序限制。下面是完成上述規則所要求的內存屏障:   這里寫圖片描述 (1)LoadLoad 屏障 執行順序:Load1—>Loadload—>Load2 確保Load2及后續Load指令加載數據之前能訪問到Load1加載的數據。

(2)StoreStore 屏障 執行順序:Store1—>StoreStore—>Store2 確保Store2以及后續Store指令執行前,Store1操作的數據對其它處理器可見。

(3)LoadStore 屏障 執行順序: Load1—>LoadStore—>Store2 確保Store2和后續Store指令執行前,可以訪問到Load1加載的數據。

(4)StoreLoad 屏障 執行順序: Store1—> StoreLoad—>Load2 確保Load2和后續的Load指令讀取之前,Store1的數據對其他處理器是可見的。

最后我可以通過一個實例來說明一下JVM中是如何插入內存屏障的:

package com.paddx.test.concurrent;public class MemoryBarrier { int a, b; volatile int v, u; void f() { int i, j; i = a; j = b; i = v; //LoadLoad j = u; //LoadStore a = i; b = j; //StoreStore v = i; //StoreStore u = j; //StoreLoad i = u; //LoadLoad //LoadStore j = b; a = i; }}

四、總結

總體上來說volatile的理解還是比較困難的,如果不是特別理解,也不用急,完全理解需要一個過程,在后續的文章中也還會多次看到volatile的使用場景。這里暫且對volatile的基礎知識和原來有一個基本的了解。總體來說,volatile是并發編程中的一種優化,在某些場景下可以代替Synchronized。但是,volatile的不能完全取代Synchronized的位置,只有在一些特殊的場景下,才能適用volatile??偟膩碚f,必須同時滿足下面兩個條件才能保證在并發環境的線程安全:

對變量的寫操作不依賴于當前值。

該變量沒有包含在具有其他變量的不變式中。

轉發至http://www.49028c.com/paddix/p/5428507.html


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久久精品一区二区三区| 中文字幕在线观看日韩| 亚洲精品久久久久中文字幕欢迎你| 欧美国产日韩中文字幕在线| 日韩欧美a级成人黄色| 在线视频国产日韩| 国产精品免费网站| 亚洲91av视频| 欧美电影免费播放| 欧美成人精品不卡视频在线观看| 中文字幕亚洲情99在线| 午夜精品在线观看| 中文字幕日韩免费视频| 91夜夜揉人人捏人人添红杏| 亚洲精品www久久久| 亚洲一级一级97网| 国产精品久久一区| 国产成人中文字幕| 精品无人区太爽高潮在线播放| 日本久久久久亚洲中字幕| 国产精品精品久久久久久| 国产成人精品免高潮费视频| 久久精品久久久久电影| 欧美电影在线播放| 欧美极品美女电影一区| 国产成人高潮免费观看精品| 亚洲第一偷拍网| 国产精品久久久久久av福利软件| 久久国产精品久久久久| 欧美精品亚州精品| 国产欧美日韩精品丝袜高跟鞋| 这里只有视频精品| 亚洲人成网站777色婷婷| 欧美日韩久久久久| 久久精品国产v日韩v亚洲| 亚洲人成电影在线观看天堂色| 欧美极品第一页| 中文字幕一区二区三区电影| 久久亚洲精品中文字幕冲田杏梨| 北条麻妃在线一区二区| 亚洲成人三级在线| 91欧美激情另类亚洲| 亚洲精品一区二区网址| 国产精品电影网| 91精品啪在线观看麻豆免费| 久久精品国产亚洲7777| 中文字幕久久精品| 综合136福利视频在线| 国产精品视频免费在线| 九九热视频这里只有精品| 亚洲综合日韩中文字幕v在线| 精品女同一区二区三区在线播放| 欧美高清视频免费观看| 欧美日韩国产在线播放| 欧美成人久久久| 国产精品一区久久久| 国产亚洲精品久久| 精品在线观看国产| 欧美日韩免费网站| 欧美中文字幕在线播放| 91免费看片网站| 深夜精品寂寞黄网站在线观看| 爱福利视频一区| 777国产偷窥盗摄精品视频| 欧美在线视频免费观看| 日韩av网址在线观看| 亚洲欧美综合v| 国产成人精品在线播放| 欧美电影免费在线观看| www国产精品com| 97色在线观看免费视频| 国产美女久久精品香蕉69| 精品人伦一区二区三区蜜桃免费| 国产精品久久久久久久久借妻| 成人性生交大片免费看小说| 777精品视频| 久久精品人人做人人爽| 国产日韩欧美日韩| 欧美一级大片视频| 国产美女被下药99| 亚洲精选在线观看| 最近2019中文字幕第三页视频| 成人深夜直播免费观看| 在线播放国产一区二区三区| 国产香蕉97碰碰久久人人| 亚洲视频欧美视频| 欧美成aaa人片免费看| 国产97在线亚洲| 亚洲视频日韩精品| 成人精品一区二区三区电影黑人| 国产在线视频2019最新视频| 日本午夜在线亚洲.国产| 日韩精品视频在线免费观看| 久久久久久亚洲精品中文字幕| 亚洲老司机av| 综合国产在线观看| 在线成人中文字幕| 一区三区二区视频| 中文字幕国产精品久久| 国产成人精品久久二区二区91| 在线观看视频99| 在线观看视频亚洲| 亚洲日本aⅴ片在线观看香蕉| 国内偷自视频区视频综合| 成人精品久久久| 久久精品成人欧美大片古装| 国产又爽又黄的激情精品视频| 久久理论片午夜琪琪电影网| 久久久久久久久久久国产| 91视频8mav| 欧美一区二区影院| 欧美在线一级va免费观看| 午夜精品久久久久久99热软件| 中文字幕亚洲综合| 久久久精品日本| 久久久久久一区二区三区| 亚洲性夜色噜噜噜7777| 日韩中文字幕免费看| 一夜七次郎国产精品亚洲| 精品国产一区二区三区四区在线观看| www.99久久热国产日韩欧美.com| 欧美夜福利tv在线| 国产成人精品电影| 亚洲性生活视频| 伊人成人开心激情综合网| 日韩精品一区二区三区第95| 最近2019中文字幕在线高清| 欧美性猛交xxxx久久久| 欧美成人国产va精品日本一级| 国内精品视频久久| 亚洲欧美日韩视频一区| 欧美性视频网站| 国产福利成人在线| 亚洲人成啪啪网站| 97国产精品免费视频| 国产精品电影网| 久久久中文字幕| 4388成人网| 久热精品视频在线免费观看| 国产精品视频一区二区三区四| 超碰精品一区二区三区乱码| 国产精品免费久久久久影院| 日韩中文字幕在线看| 国产97免费视| 国产精品永久免费| 欧美肥婆姓交大片| 日韩美女免费视频| 精品亚洲永久免费精品| 久久免费精品日本久久中文字幕| 亚洲欧美激情四射在线日| 国产精品h片在线播放| 日本视频久久久| 国产精品美女无圣光视频| 青青草国产精品一区二区| 色综合伊人色综合网站| 日韩在线观看电影| 成人动漫网站在线观看| 国产精品一区二区久久久久| 国产一区二区激情| 日韩在线观看高清| 视频在线一区二区| 午夜精品一区二区三区在线视| 夜夜嗨av色一区二区不卡| 亚洲女人天堂色在线7777|