久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

【Java線程】Java內(nèi)存模型總結(jié)

 小新丸子rdyfps 2016-07-22

學(xué)習(xí)資料:http://www./cn/articles/java-memory-model-1


Java的并發(fā)采用的是共享內(nèi)存模型(而非消息傳遞模型),,線程之間共享程序的公共狀態(tài),線程之間通過寫-讀內(nèi)存中的公共狀態(tài)來隱式進(jìn)行通信,。多個線程之間是不能直接傳遞數(shù)據(jù)交互的,,它們之間的交互只能通過共享變量來實現(xiàn)

同步顯式進(jìn)行的。程序員必須顯式指定某個方法或某段代碼需要在線程之間互斥執(zhí)行,。

1,、多線程通信

1.1 內(nèi)存模型

Java線程之間的通信由Java內(nèi)存模型(JMM)控制,JMM決定一個線程對共享變量的寫入何時對另一個線程可見。

從抽象的角度來看,,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(main memory)中,,每個線程都有一個私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本,。本地內(nèi)存是JMM的一個抽象概念,,并不真實存在,它涵蓋了緩存,,寫緩沖區(qū),,寄存器以及其他的硬件和編譯器優(yōu)化。Java內(nèi)存模型的抽象示意圖如下:


線程間通信的步驟:

  1. 首先,,線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去,。
  2. 然后,線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量,。

  • 本地內(nèi)存A和B有主內(nèi)存中共享變量x的副本,。
  • 假設(shè)初始時,這三個內(nèi)存中的x值都為0,。線程A在執(zhí)行時,,把更新后的x值(假設(shè)值為1)臨時存放在自己的本地內(nèi)存A中。
  • 當(dāng)線程A和線程B需要通信時(如何激發(fā),?--隱式),,線程A首先會把自己本地內(nèi)存中修改后的x值刷新到主內(nèi)存中,此時主內(nèi)存中的x值變?yōu)榱?,。
  • 隨后,,線程B到主內(nèi)存中去讀取線程A更新后的x值,此時線程B的本地內(nèi)存的x值也變?yōu)榱?,。

從整體來看,,這兩個步驟實質(zhì)上是線程A在向線程B發(fā)送消息,而且這個通信過程必須要經(jīng)過主內(nèi)存,。JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互,,來為java程序員提供內(nèi)存可見性保證。


1.2 可見性,、有序性

例如在多個線程之間共享了Count類的一個對象,,這個對象是被創(chuàng)建在主內(nèi)存(堆內(nèi)存)中,每個線程都有自己的本地內(nèi)存(線程棧),,工作內(nèi)存存儲了主內(nèi)存Count對象的一個副本,當(dāng)線程操作Count對象時,,首先從主內(nèi)存復(fù)制Count對象到工作內(nèi)存中,,然后執(zhí)行代碼count.count(),改變了num值,,最后用工作內(nèi)存Count刷新主內(nèi)存Count,。

當(dāng)一個對象在多個內(nèi)存中都存在副本時,,如果一個內(nèi)存修改了共享變量,其它線程也應(yīng)該能夠看到被修改后的值,,此為可見性,。


一個運(yùn)算賦值操作并不是一個原子性操作,多個線程執(zhí)行時,,CPU對線程的調(diào)度是隨機(jī)的,,我們不知道當(dāng)前程序被執(zhí)行到哪步就切換到了下一個線程,一個最經(jīng)典的例子就是銀行匯款問題,,一個銀行賬戶存款100,,這時一個人從該賬戶取10元,同時另一個人向該賬戶匯10元,,那么余額應(yīng)該還是100,。那么此時可能發(fā)生這種情況,A線程負(fù)責(zé)取款,,B線程負(fù)責(zé)匯款,,A從主內(nèi)存讀到100,B從主內(nèi)存讀到100,,A執(zhí)行減10操作,,并將數(shù)據(jù)刷新到主內(nèi)存,這時主內(nèi)存數(shù)據(jù)100-10=90,,而B內(nèi)存執(zhí)行加10操作,,并將數(shù)據(jù)刷新到主內(nèi)存,最后主內(nèi)存數(shù)據(jù)100 10=110,,顯然這是一個嚴(yán)重的問題,,我們要保證A線程和B線程有序執(zhí)行,先取款后匯款或者先匯款后取款,,此為有序性,。


1.3 synchronized與volatile

一個線程執(zhí)行互斥代碼過程如下:

  1. 獲得同步鎖;
  2. 清空工作內(nèi)存,;
  3. 從主內(nèi)存拷貝對象副本到工作內(nèi)存,;
  4.  執(zhí)行代碼(計算或者輸出等);
  5. 刷新主內(nèi)存數(shù)據(jù),;
  6. 釋放同步鎖,。

所以,synchronized既保證了多線程的并發(fā)有序性,,又保證了多線程的內(nèi)存可見性,。


volatile是第二種Java多線程同步的手段,根據(jù)JLS的說法,一個變量可以被volatile修飾,,在這種情況下內(nèi)存模型確保所有線程可以看到一致的變量值

class Test { static volatile int i = 0, j = 0; static void one() { i ; j ; } static void two() { System.out.println('i=' i ' j=' j); } }


加上volatile可以將共享變量i和j的改變直接響應(yīng)到主內(nèi)存中,,這樣保證了i和j的值可以保持一致,然而我們不能保證執(zhí)行two方法的線程是在i和j執(zhí)行到什么程度獲取到的,,所以volatile可以保證內(nèi)存可見性,,不能保證并發(fā)有序性


如果沒有volatile,,則代碼執(zhí)行過程如下:

  1. 將變量i從主內(nèi)存拷貝到工作內(nèi)存,;

  2. 刷新主內(nèi)存數(shù)據(jù);

  3. 改變i的值,;
  4. 將變量j從主內(nèi)存拷貝到工作內(nèi)存,;

  5. 刷新主內(nèi)存數(shù)據(jù);

  6. 改變j的值,;

       


2,、重排序

JMM屬于語言級的內(nèi)存模型,它確保在不同的編譯器和不同的處理器平臺之上,,通過禁止特定類型的編譯器重排序和處理器重排序,,為程序員提供一致的內(nèi)存可見性保證。

對于編譯器沖排序,,JMM的編譯器重排序規(guī)則會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止),。

對于處理器重排序,JMM的處理器重排序規(guī)則會要求java編譯器在生成指令序列時,,插入特定類型的內(nèi)存屏障(memory barriers,,intel稱之為memory fence)指令,通過內(nèi)存屏障指令來禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止),。


引申:

在執(zhí)行程序時為了提高性能,,編譯器和處理器常常會對指令做重排序。重排序分三種類型:

  1. 編譯器優(yōu)化的重排序,。編譯器在不改變單線程程序語義的前提下,,可以重新安排語句的執(zhí)行順序。
  2. 指令級并行的重排序?,F(xiàn)代處理器采用了指令級并行技術(shù)(Instruction-Level Parallelism,, ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,,處理器可以改變語句對應(yīng)機(jī)器指令的執(zhí)行順序,。
  3. 內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū),,這使得加載和存儲操作看上去可能是在亂序執(zhí)行,。

上述的1屬于編譯器重排序,,2和3屬于處理器重排序,。這些重排序都可能會導(dǎo)致多線程程序出現(xiàn)內(nèi)存可見性問題,。


2.1 數(shù)據(jù)依賴性

如果兩個操作訪問同一個變量,且這兩個操作中有一個為寫操作,,此時這兩個操作之間就存在數(shù)據(jù)依賴性,。數(shù)據(jù)依賴分下列三種類型:

名稱 代碼示例 說明
寫后讀 a = 1;b = a; 寫一個變量之后,再讀這個位置,。
寫后寫 a = 1;a = 2; 寫一個變量之后,,再寫這個變量。
讀后寫 a = b;b = 1; 讀一個變量之后,,再寫這個變量,。

上面三種情況,只要重排序兩個操作的執(zhí)行順序,,程序的執(zhí)行結(jié)果將會被改變,。


前面提到過,編譯器和處理器可能會對操作做重排序,。編譯器和處理器在重排序時,,會遵守數(shù)據(jù)依賴性,編譯器和處理器不會改變存在數(shù)據(jù)依賴關(guān)系的兩個操作的執(zhí)行順序,。

注意,,這里所說的數(shù)據(jù)依賴性僅針對單個處理器中執(zhí)行的指令序列和單個線程中執(zhí)行的操作,不同處理器之間和不同線程之間的數(shù)據(jù)依賴性不被編譯器和處理器考慮,。

2.2 as-if-serial語義

as-if-serial語義的意思指:不管怎么重排序(編譯器和處理器為了提高并行度),,(單線程)程序的執(zhí)行結(jié)果不能被改變。編譯器,,runtime 和處理器都必須遵守as-if-serial語義,。

【例】

double pi = 3.14; //A double r = 1.0; //B double area = pi * r * r; //C

上面三個操作的數(shù)據(jù)依賴關(guān)系如下圖所示:

如上圖所示,A和C之間存在數(shù)據(jù)依賴關(guān)系,,同時B和C之間也存在數(shù)據(jù)依賴關(guān)系,。因此在最終執(zhí)行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,,程序的結(jié)果將會被改變),。但A和B之間沒有數(shù)據(jù)依賴關(guān)系,編譯器和處理器可以重排序A和B之間的執(zhí)行順序,。下圖是該程序的兩種執(zhí)行順序:


as-if-serial語義把單線程程序保護(hù)了起來,,遵守as-if-serial語義的編譯器,runtime 和處理器共同為編寫單線程程序的程序員創(chuàng)建了一個幻覺:單線程程序是按程序的順序來執(zhí)行的,。as-if-serial語義使單線程程序員無需擔(dān)心重排序會干擾他們,,也無需擔(dān)心內(nèi)存可見性問題,。

2.3 happens-before

從JDK5開始,java使用新的JSR -133內(nèi)存模型,。JSR-133提出了happens-before的概念,,通過這個概念來闡述操作之間的內(nèi)存可見性如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見,,那么這兩個操作之間必須存在happens-before關(guān)系,。這里提到的兩個操作既可以是在一個線程之內(nèi),也可以是在不同線程之間,。 與程序員密切相關(guān)的happens-before規(guī)則如下:

  • 程序順序規(guī)則:一個線程中的每個操作,,happens- before 于該線程中的任意后續(xù)操作。
  • 監(jiān)視器鎖規(guī)則:對一個監(jiān)視器鎖的解鎖,,happens- before 于隨后對這個監(jiān)視器鎖的加鎖,。
  • volatile變量規(guī)則:對一個volatile域的寫,happens- before 于任意后續(xù)對這個volatile域的讀,。
  • 傳遞性:如果A happens- before B,,且B happens- before C,那么A happens- before C,。

注意,,兩個操作之間具有happens-before關(guān)系,并不意味著前一個操作必須要在后一個操作之前執(zhí)行,!happens-before僅僅要求前一個操作(執(zhí)行的結(jié)果)對后一個操作可見,,且前一個操作按順序排在第二個操作之前(the first is visible to and ordered before the second)。happens- before的定義很微妙,,后文會具體說明happens-before為什么要這么定義,。


【例】根據(jù)happens- before的程序順序規(guī)則,上面計算圓的面積的示例代碼存在三個happens- before關(guān)系:

  1. A happens- before B,;
  2. B happens- before C,;
  3. A happens- before C;

這里的第3個happens- before關(guān)系,,是根據(jù)happens- before的傳遞性推導(dǎo)出來的,。

這里A happens- before B,但實際執(zhí)行時B卻可以排在A之前執(zhí)行(看上面的重排序后的執(zhí)行順序),。A happens- before B,,JMM并不要求A一定要在B之前執(zhí)行。JMM僅僅要求前一個操作(執(zhí)行的結(jié)果)對后一個操作可見,,且前一個操作按順序排在第二個操作之前,。這里操作A的執(zhí)行結(jié)果不需要對操作B可見;而且重排序操作A和操作B后的執(zhí)行結(jié)果,,與操作A和操作B按happens- before順序執(zhí)行的結(jié)果一致,。在這種情況下,,JMM會認(rèn)為這種重排序并不非法(not illegal),JMM允許這種重排序,。

在計算機(jī)中,,軟件技術(shù)和硬件技術(shù)有一個共同的目標(biāo):在不改變程序執(zhí)行結(jié)果的前提下,盡可能的開發(fā)并行度,。編譯器和處理器遵從這一目標(biāo),,從happens- before的定義我們可以看出,JMM同樣遵從這一目標(biāo),。

2.4 重排序?qū)Χ嗑€程的影響

現(xiàn)在讓我們來看看,重排序是否會改變多線程程序的執(zhí)行結(jié)果,?!纠浚?/p>

class ReorderExample { int a = 0; boolean flag = false; public void writer() { a = 1; //1 flag = true; //2 } Public void reader() { if (flag) { //3 int i = a * a; //4 …… } } }

flag變量是個標(biāo)記,用來標(biāo)識變量a是否已被寫入,。這里假設(shè)有兩個線程A和B,,A首先執(zhí)行writer()方法,隨后B線程接著執(zhí)行reader()方法,。線程B在執(zhí)行操作4時,,能否看到線程A在操作1對共享變量a的寫入?

答案是:不一定能看到,。


由于操作1和操作2沒有數(shù)據(jù)依賴關(guān)系,,編譯器和處理器可以對這兩個操作重排序;同樣,,操作3和操作4沒有數(shù)據(jù)依賴關(guān)系(,?),編譯器和處理器也可以對這兩個操作重排序,。讓我們先來看看,,當(dāng)操作1和操作2重排序時,可能會產(chǎn)生什么效果,?請看下面的程序執(zhí)行時序圖:


如上圖所示,,操作1和操作2做了重排序。程序執(zhí)行時,,線程A首先寫標(biāo)記變量flag,,隨后線程B讀這個變量。由于條件判斷為真,,線程B將讀取變量a,。此時,變量a還根本沒有被線程A寫入,,在這里多線程程序的語義被重排序破壞了,!


下面再讓我們看看,,當(dāng)操作3和操作4重排序時會產(chǎn)生什么效果(借助這個重排序,可以順便說明控制依賴性),。下面是操作3和操作4重排序后,,程序的執(zhí)行時序圖:


在程序中,操作3和操作4存在控制依賴關(guān)系,。當(dāng)代碼中存在控制依賴性時,,會影響指令序列執(zhí)行的并行度。為此,,編譯器和處理器會采用猜測(Speculation)執(zhí)行來克服控制相關(guān)性對并行度的影響,。以處理器的猜測執(zhí)行為例,執(zhí)行線程B的處理器可以提前讀取并計算a*a,,然后把計算結(jié)果臨時保存到一個名為重排序緩沖(reorder buffer ROB)的硬件緩存中,。當(dāng)接下來操作3的條件判斷為真時,就把該計算結(jié)果寫入變量i中,。

從圖中我們可以看出,,猜測執(zhí)行實質(zhì)上對操作3和4做了重排序。重排序在這里破壞了多線程程序的語義,!

在單線程程序中,,對存在控制依賴的操作重排序,不會改變執(zhí)行結(jié)果(這也是as-if-serial語義允許對存在控制依賴的操作做重排序的原因),;但在多線程程序中,,對存在控制依賴的操作重排序,可能會改變程序的執(zhí)行結(jié)果,。


3,、順序一致性

3.1 數(shù)據(jù)競爭

當(dāng)程序未正確同步時,就會存在數(shù)據(jù)競爭,。java內(nèi)存模型規(guī)范對數(shù)據(jù)競爭的定義如下:

  • 在一個線程中寫一個變量,,
  • 在另一個線程讀同一個變量,
  • 而且寫和讀沒有通過同步來排序,。

當(dāng)代碼中包含數(shù)據(jù)競爭時,,程序的執(zhí)行往往產(chǎn)生違反直覺的結(jié)果(前一章的示例正是如此)。如果一個多線程程序能正確同步,,這個程序?qū)⑹且粋€沒有數(shù)據(jù)競爭的程序,。

JMM對正確同步的多線程程序的內(nèi)存一致性做了如下保證:

  • 如果程序是正確同步的,程序的執(zhí)行將具有順序一致性(sequentially consistent)——即程序的執(zhí)行結(jié)果與該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同,。這里的同步是指廣義上的同步,,包括對常用同步原語(lock,volatile和final)的正確使用,。

3.2 順序一致性內(nèi)存模型

順序一致性內(nèi)存模型有兩大特性:

  • 一個線程中的所有操作必須按照程序的順序來執(zhí)行,。
  • (不管程序是否同步)所有線程都只能看到一個單一的操作執(zhí)行順序,。在順序一致性內(nèi)存模型中,每個操作都必須原子執(zhí)行且立刻對所有線程可見,。

順序一致性內(nèi)存模型為程序員提供的視圖如下,。在概念上,順序一致性模型有一個單一的全局內(nèi)存,,這個內(nèi)存通過一個左右擺動的開關(guān)可以連接到任意一個線程,。同時,每一個線程必須按程序的順序來執(zhí)行內(nèi)存讀/寫操作,。在任意時間點(diǎn)最多只能有一個線程可以連接到內(nèi)存,。當(dāng)多個線程并發(fā)執(zhí)行時,圖中的開關(guān)裝置能把所有線程的所有內(nèi)存讀/寫操作串行化,。


為了更好的理解,,下面我們通過兩個示意圖來對順序一致性模型的特性做進(jìn)一步的說明。

假設(shè)有兩個線程A和B并發(fā)執(zhí)行,。其中A線程有三個操作,它們在程序中的順序是:A1->A2->A3,。B線程也有三個操作,,它們在程序中的順序是:B1->B2->B3。

假設(shè)這兩個線程使用監(jiān)視器來正確同步:A線程的三個操作執(zhí)行后釋放監(jiān)視器,,隨后B線程獲取同一個監(jiān)視器,。那么程序在順序一致性模型中的執(zhí)行效果將如下圖所示:

假設(shè)這兩個線程沒有做同步,下面是這個未同步程序在順序一致性模型中的執(zhí)行示意圖:


未同步程序在順序一致性模型中雖然整體執(zhí)行順序是無序的,,但所有線程都只能看到一個一致的整體執(zhí)行順序,。以上圖為例,線程A和B看到的執(zhí)行順序都是:B1->A1->A2->B2->A3->B3,。之所以能得到這個保證是因為順序一致性內(nèi)存模型中的每個操作必須立即對任意線程可見,。

但是,在JMM中就沒有這個保證,。未同步程序在JMM中不但整體的執(zhí)行順序是無序的,,而且所有線程看到的操作執(zhí)行順序也可能不一致。比如,,在當(dāng)前線程把寫過的數(shù)據(jù)緩存在本地內(nèi)存中,,且還沒有刷新到主內(nèi)存之前,這個寫操作僅對當(dāng)前線程可見,;從其他線程的角度來觀察,,會認(rèn)為這個寫操作根本還沒有被當(dāng)前線程執(zhí)行。只有當(dāng)前線程把本地內(nèi)存中寫過的數(shù)據(jù)刷新到主內(nèi)存之后,,這個寫操作才能對其他線程可見,。在這種情況下,,當(dāng)前線程和其它線程看到的操作執(zhí)行順序?qū)⒉灰恢隆?/p>

3.3 同步程序的執(zhí)行特性

【例】

class SynchronizedExample { int a = 0; boolean flag = false; public synchronized void writer() { a = 1; flag = true; } public synchronized void reader() { if (flag) { int i = a; …… } } }


在順序一致性模型中,所有操作完全按程序的順序串行執(zhí)行,。而在JMM中,,臨界區(qū)內(nèi)的代碼可以重排序。

3.4 未同步程序的執(zhí)行特性

對于未同步或未正確同步的多線程程序,,JMM只提供最小安全性:線程執(zhí)行時讀取到的值,,要么是之前某個線程寫入的值,要么是默認(rèn)值(0,,null,,false),JMM保證線程讀操作讀取到的值不會無中生有(out of thin air)的冒出來,。

為了實現(xiàn)最小安全性,,JVM在堆上分配對象時,首先會清零內(nèi)存空間,,然后才會在上面分配對象(JVM內(nèi)部會同步這兩個操作),。因此,在以清零的內(nèi)存空間(pre-zeroed memory)分配對象時,,域的默認(rèn)初始化已經(jīng)完成了,。


JMM不保證未同步程序的執(zhí)行結(jié)果與該程序在順序一致性模型中的執(zhí)行結(jié)果一致。因為未同步程序在順序一致性模型中執(zhí)行時,,整體上是無序的,,其執(zhí)行結(jié)果無法預(yù)知。保證未同步程序在兩個模型中的執(zhí)行結(jié)果一致毫無意義,。

和順序一致性模型一樣,,未同步程序在JMM中的執(zhí)行時,整體上也是無序的,,其執(zhí)行結(jié)果也無法預(yù)知,。同時,未同步程序在這兩個模型中的執(zhí)行特性有下面幾個差異

  1. 順序一致性模型保證單線程內(nèi)的操作會按程序的順序執(zhí)行,,而JMM不保證單線程內(nèi)的操作會按程序的順序執(zhí)行(比如上面正確同步的多線程程序在臨界區(qū)內(nèi)的重排序),。——前文已述
  2. 順序一致性模型保證所有線程只能看到一致的操作執(zhí)行順序,,而JMM不保證所有線程能看到一致的操作執(zhí)行順序,。——前文已述
  3. JMM不保證對64位的long型和double型變量的讀/寫操作具有原子性,,而順序一致性模型保證對所有的內(nèi)存讀/寫操作都具有原子性,。
關(guān)于第三點(diǎn):

第三點(diǎn)差異與處理器總線的工作機(jī)制密切相關(guān)。在計算機(jī)中,數(shù)據(jù)通過總線在處理器和內(nèi)存之間傳遞,。每次處理器和內(nèi)存之間的數(shù)據(jù)傳遞都是通過一系列步驟來完成的,,這一系列步驟稱之為總線事務(wù)(bus transaction)??偩€事務(wù)包括讀事務(wù)(read transaction)和寫事務(wù)(write transaction),。讀事務(wù)從內(nèi)存?zhèn)魉蛿?shù)據(jù)到處理器,寫事務(wù)從處理器傳送數(shù)據(jù)到內(nèi)存,,每個事務(wù)會讀/寫內(nèi)存中一個或多個物理上連續(xù)的字,。這里的關(guān)鍵是,總線會同步試圖并發(fā)使用總線的事務(wù),。在一個處理器執(zhí)行總線事務(wù)期間,,總線會禁止其它所有的處理器和I/O設(shè)備執(zhí)行內(nèi)存的讀/寫。

在一些32位的處理器上,,如果要求對64位數(shù)據(jù)的讀/寫操作具有原子性,,會有比較大的開銷。為了照顧這種處理器,,java語言規(guī)范鼓勵但不強(qiáng)求JVM對64位的long型變量和double型變量的讀/寫具有原子性,。當(dāng)JVM在這種處理器上運(yùn)行時,會把一個64位long/ double型變量的讀/寫操作拆分為兩個32位的讀/寫操作來執(zhí)行,。這兩個32位的讀/寫操作可能會被分配到不同的總線事務(wù)中執(zhí)行,,此時對這個64位變量的讀/寫將不具有原子性。

當(dāng)單個內(nèi)存操作不具有原子性,,將可能會產(chǎn)生意想不到后果。請看下面示意圖:

如上圖所示,,假設(shè)處理器A寫一個long型變量,,同時處理器B要讀這個long型變量。處理器A中64位的寫操作被拆分為兩個32位的寫操作,,且這兩個32位的寫操作被分配到不同的寫事務(wù)中執(zhí)行,。同時處理器B中64位的讀操作被拆分為兩個32位的讀操作,且這兩個32位的讀操作被分配到同一個的讀事務(wù)中執(zhí)行,。當(dāng)處理器A和B按上圖的時序來執(zhí)行時,,處理器B將看到僅僅被處理器A“寫了一半“的無效值。


4,、volatile

把對volatile變量的單個讀/寫,,看成是使用同一個監(jiān)視器鎖對這些單個讀/寫操作做了同步。對一個volatile變量的讀,,總是能看到(任意線程)對這個volatile變量最后的寫入,。

這意味著即使是64位的long型和double型變量,只要它是volatile變量,對該變量的讀寫就將具有原子性,。如果是多個volatile操作或類似于volatile 這種復(fù)合操作,,這些操作整體上不具有原子性。

簡而言之,,volatile變量自身具有下列特性:

  • 可見性,。對一個volatile變量的讀,總是能看到(任意線程)對這個volatile變量最后的寫入,。
  • 原子性:對任意單個volatile變量的讀/寫具有原子性,,但類似于volatile 這種復(fù)合操作不具有原子性。

4.1 volatile寫-讀建立的happens before關(guān)系

從JSR-133開始,,volatile變量的寫-讀可以實現(xiàn)線程之間的通信,。

從內(nèi)存語義的角度來說,volatile與監(jiān)視器鎖有相同的效果:volatile寫和監(jiān)視器的釋放有相同的內(nèi)存語義,;volatile讀與監(jiān)視器的獲取有相同的內(nèi)存語義,。

class VolatileExample { int a = 0; volatile boolean flag = false; public void writer() { a = 1; //1 flag = true; //2 } public void reader() { if (flag) { //3 int i = a; //4 …… } } }

假設(shè)線程A執(zhí)行writer()方法之后,線程B執(zhí)行reader()方法,。根據(jù)happens before規(guī)則,,這個過程建立的happens before 關(guān)系可以分為兩類:

  1. 根據(jù)程序次序規(guī)則,1 happens before 2; 3 happens before 4,。
  2. 根據(jù)volatile規(guī)則,,2 happens before 3。
  3. 根據(jù)happens before 的傳遞性規(guī)則,,1 happens before 4,。

 

 

 上圖中,每一個箭頭鏈接的兩個節(jié)點(diǎn),,代表了一個happens before 關(guān)系,。黑色箭頭表示程序順序規(guī)則;橙色箭頭表示volatile規(guī)則,;藍(lán)色箭頭表示組合這些規(guī)則后提供的happens before保證,。

這里A線程寫一個volatile變量后,B線程讀同一個volatile變量,。A線程在寫volatile變量之前所有可見的共享變量,,在B線程讀同一個volatile變量后,將立即變得對B線程可見,。

 

4.2 volatile寫-讀的內(nèi)存語義

 volatile寫的內(nèi)存語義如下:

  • 當(dāng)寫一個volatile變量時,,JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存。

以上面示例程序VolatileExample為例,,假設(shè)線程A首先執(zhí)行writer()方法,,隨后線程B執(zhí)行reader()方法,初始時兩個線程的本地內(nèi)存中的flag和a都是初始狀態(tài)。

下圖是線程A執(zhí)行volatile寫后,,共享變量的狀態(tài)示意圖,。線程A在寫flag變量后,本地內(nèi)存A中被線程A更新過的兩個共享變量的值被刷新到主內(nèi)存中,。此時,,本地內(nèi)存A和主內(nèi)存中的共享變量的值是一致的。

 

volatile讀的內(nèi)存語義如下:

  • 當(dāng)讀一個volatile變量時,,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效,。線程接下來將從主內(nèi)存中讀取共享變量。

下面是線程B讀同一個volatile變量后,,共享變量的狀態(tài)示意圖,。在讀flag變量后,本地內(nèi)存B已經(jīng)被置為無效,。此時,,線程B必須從主內(nèi)存中讀取共享變量。線程B的讀取操作將導(dǎo)致本地內(nèi)存B與主內(nèi)存中的共享變量的值也變成一致的了,。

把volatile寫和volatile讀這兩個步驟綜合起來看的話,,在讀線程B讀一個volatile變量后,寫線程A在寫這個volatile變量之前所有可見的共享變量的值都將立即變得對讀線程B可見,。

下面對volatile寫和volatile讀的內(nèi)存語義做個總結(jié):

  • 線程A寫一個volatile變量,,實質(zhì)上是線程A向接下來將要讀這個volatile變量的某個線程發(fā)出了(其對共享變量所在修改的)消息。
  • 線程B讀一個volatile變量,,實質(zhì)上是線程B接收了之前某個線程發(fā)出的(在寫這個volatile變量之前對共享變量所做修改的)消息,。
  • 線程A寫一個volatile變量,隨后線程B讀這個volatile變量,,這個過程實質(zhì)上是線程A通過主內(nèi)存向線程B發(fā)送消息,。

4.3 volatile內(nèi)存語義的實現(xiàn)

為了實現(xiàn)volatile內(nèi)存語義,JMM會分別限制編譯器重排序和處理器重排序,。下面是JMM針對編譯器制定的volatile重排序規(guī)則表:

 

是否能重排序 第二個操作
第一個操作 普通讀/寫 volatile讀 volatile寫
普通讀/寫     NO
volatile讀 NO NO NO
volatile寫   NO NO

舉例來說,第三行最后一個單元格的意思是:在程序順序中,,當(dāng)?shù)谝粋€操作為普通變量的讀或?qū)憰r,,如果第二個操作為volatile寫,則編譯器不能重排序這兩個操作,。

從上表我們可以看出:

  • 當(dāng)?shù)诙€操作是volatile寫時,,不管第一個操作是什么,都不能重排序,。這個規(guī)則確保volatile寫之前的操作不會被編譯器重排序到volatile寫之后,。
  • 當(dāng)?shù)谝粋€操作是volatile讀時,不管第二個操作是什么,都不能重排序,。這個規(guī)則確保volatile讀之后的操作不會被編譯器重排序到volatile讀之前,。
  • 當(dāng)?shù)谝粋€操作是volatile寫,第二個操作是volatile讀時,,不能重排序,。

 

 

 






    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多