摘要:順序一致性內(nèi)存模型有兩大特性一個(gè)線程中所有操作必須按照程序的順序執(zhí)行。這里的同步包括對(duì)常用同步原語(yǔ)的正確使用通過(guò)以下程序說(shuō)明與順序一致性兩種內(nèi)存模型的對(duì)比順序一致性模型中所有操作完全按程序的順序串行執(zhí)行。
java內(nèi)存模型 java內(nèi)存模型基礎(chǔ) happen-before模型
JSR-133使用happen-before的概念來(lái)闡述操作之間的內(nèi)存可見(jiàn)性。在JMM中,如果一個(gè)操作執(zhí)行的結(jié)果需要對(duì)另一個(gè)操作可見(jiàn),那么這兩個(gè)操作之間必須要存在happen-before關(guān)系。在這里兩個(gè)操作可以在一個(gè)線程之內(nèi),也可以在不同的線程之間。與程序員相關(guān)的happen-before規(guī)則如下:
程序順序一致性:一個(gè)線程中的每個(gè)操作,happen-before于該線程中的任意后續(xù)操作。(不要扣字,若兩操作沒(méi)有依賴(lài)關(guān)系,且變更操作順序不影響結(jié)果,此時(shí)順序可以變更。與程序一致性規(guī)則不沖突)
監(jiān)視器鎖規(guī)則: 對(duì)一個(gè)鎖的解鎖,happen-before于隨后對(duì)這個(gè)鎖的加鎖。
volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫(xiě)操作,happen-before于任意后續(xù)對(duì)這個(gè)volatile域的讀。
傳遞性:如果A happen-before B且B happen-before C ,那么A happen-before C。
start()規(guī)則:如果線程A執(zhí)行操作ThreadB.start(),那么A線程的ThreadB.start()操作happen-before于B中的任何操作。
join()規(guī)則:如果ThreadA 執(zhí)行操作ThreadB.join()并成功返回,那么ThreadB中的任意操作happen-before于ThreadA從ThreadB.join()操作成功返回。
兩個(gè)操作間具有happens-before關(guān)系,并不意味著前一個(gè)操作必須要在后一個(gè)操作之前執(zhí)行。happens-before僅僅要求前一個(gè)操作對(duì)后一個(gè)操作可見(jiàn)。
重排序重排序是指編譯器和處理器為了優(yōu)化程序性能而對(duì)指令序列進(jìn)行重新排序的一種手段。重排序得遵循以下原則。
數(shù)據(jù)相互信賴(lài)的兩個(gè)操作不能進(jìn)行重排序
as-if-serial語(yǔ)言,不管怎么得排序(編譯器和處理器為了提高并行度),單線程程序執(zhí)行的結(jié)果不能改變。
重排序?qū)Χ嗑€程的影響,代碼如下:
/** * 操作1 操作2 之間無(wú)依賴(lài)關(guān)系, 可以進(jìn)行重排序 * 操作3 操作4 之間無(wú)依賴(lài)關(guān)系, 可以進(jìn)行重排序 * Thread B 中并不一定能看到Thread A 中對(duì)共享變量的寫(xiě)入。此時(shí)重排序操作破壞多線程語(yǔ)義 **/ class ReorderExample{ int a = 0; boolean flag = false; public void writer(){ //Thread A a = 1; //1 flag = true; //2 } public void reader(){ //Thread B if(flag){ //3 int i = a * a; //4 } } }順序一致性
順序一致性內(nèi)存模型是一個(gè)理論參考模型,在設(shè)計(jì)的時(shí)候,處理器的內(nèi)存模型和編程語(yǔ)言的內(nèi)存模型都會(huì)以順序一致性內(nèi)存模型作為參照。順序一致性內(nèi)存模型有兩大特性:
一個(gè)線程中所有操作必須按照程序的順序執(zhí)行。
(不管程序是否同步)所有線程都只能看到單一的操作執(zhí)行順序,在順序一致性內(nèi)存模型中,每個(gè)操作都必須原子執(zhí)行且立即對(duì)所有線程可見(jiàn)。
JMM對(duì)正確同步的多線程程序的內(nèi)存一致性做了如下保證
如果程序是正確同步的,程序的執(zhí)行將具有順序一致性(Sequentially Consistent)--即程序的執(zhí)行結(jié)果與該程序在順序一致性內(nèi)存模型中執(zhí)行結(jié)果相同。這里的同步包括對(duì)常用同步原語(yǔ)(Synchronized,volatile,final)的正確使用
通過(guò)以下程序說(shuō)明JMM與順序一致性 兩種內(nèi)存模型的對(duì)比
/** *順序一致性模型中,所有操作完全按程序的順序串行執(zhí)行。而在JMM中,臨界區(qū)內(nèi)的代碼 *可以重排序(但JMM不允許臨界區(qū)內(nèi)的代碼“逸出”到臨界區(qū)之外,那樣會(huì)破壞監(jiān)視器的語(yǔ) *義)。JMM會(huì)在退出臨界區(qū)和進(jìn)入臨界區(qū)這兩個(gè)關(guān)鍵時(shí)間點(diǎn)做一些特別處理,使得線程在這兩 *個(gè)時(shí)間點(diǎn)具有與順序一致性模型相同的內(nèi)存視圖,雖然線程A在臨界 *區(qū)內(nèi)做了重排序,但由于監(jiān)視器互斥執(zhí)行的特性,這里的線程B根本無(wú)法“觀察”到線程A在臨 *界區(qū)內(nèi)的重排序。這種重排序既提高了執(zhí)行效率,又沒(méi)有改變程序的執(zhí)行結(jié)果。 * */ 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; ...... } // 釋放鎖 }
未同步程序在JMM中的執(zhí)行時(shí),整體上是無(wú)序的,其執(zhí)行結(jié)果無(wú)法預(yù)知。
順序一致性模型保證單線程內(nèi)的操作會(huì)按程序的順序執(zhí)行,而JMM不保證單線程內(nèi)的操作會(huì)按程序的順序執(zhí)行(比如上面正確同步的多線程程序在臨界區(qū)內(nèi)的重排序).
順序一致性模型保證所有線程只能看到一致的操作執(zhí)行順序,而JMM不保證所有線程能看到一致的操作執(zhí)行順序。
JMM不保證對(duì)64位的long型和double型變量的寫(xiě)操作具有原子性,而順序一致性模型保證對(duì)所有的內(nèi)存讀/寫(xiě)操作都具有原子性。
volatile內(nèi)存語(yǔ)義 volatile特性可見(jiàn)性:對(duì)一個(gè)volatile變量的讀,總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫(xiě)入。
原子性: 對(duì)任意單個(gè)volatile變量的讀/寫(xiě)具有原子性,但類(lèi)似于volatile++這種復(fù)合操作不具有原子性。
class VolatileFeaturesExample { volatile long vl = 0L; public void set(long l) { vl = l; } public void getAndIncrement () { //復(fù)合volatile讀寫(xiě),不具有線程安全 vl++; } public long get() { return vl; } }volatile 寫(xiě)-讀建立的happen-before關(guān)系
示例代碼如下:
根據(jù)程序次序規(guī)則,1 happen-before 2, 3 happen-before 4.(疑問(wèn)1:1與2,3與4兩操作沒(méi)有依賴(lài),為何不能重排,若發(fā)生重排,結(jié)果有可能會(huì)發(fā)生變化)
根據(jù)volatile規(guī)則 2 happen-before 3
根據(jù)happen-before傳遞性規(guī)則,1 happen-before 4.
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 ...... } }volatile 寫(xiě)-讀內(nèi)存原語(yǔ)
volatile寫(xiě)操作,JMM會(huì)把線程對(duì)應(yīng)的本地內(nèi)存中的共享變量值刷新到主內(nèi)存。
volatile讀操作,JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無(wú)效。線程接下來(lái)將從主內(nèi)存中讀取共享變量。
為了實(shí)現(xiàn)volatile內(nèi)存語(yǔ)義,JMM分分別限制這兩種重排序類(lèi)型,下圖JMM針對(duì)編譯器制定的volatile重排序規(guī)則表
這個(gè)重排序規(guī)則解釋了疑問(wèn)1。實(shí)現(xiàn):是通過(guò)編譯器生成字節(jié)碼時(shí),插入內(nèi)存屏障來(lái)達(dá)到這個(gè)限制,在此處不作展開(kāi),有興趣可以查閱相關(guān)資料
JSR-133增強(qiáng)volatile的內(nèi)存原語(yǔ)在JSR-133之前的舊Java內(nèi)存模型中,雖然不允許volatile變量之間重排序,但舊的Java內(nèi)存模型允許volatile變量與普通變量重排序
因此,在舊的內(nèi)存模型中,volatile的寫(xiě)-讀沒(méi)有鎖的釋放-獲所具有的內(nèi)存語(yǔ)義。為了提供一種比鎖更輕量級(jí)的線程之間通信的機(jī)制,JSR-133專(zhuān)家組決定增強(qiáng)volatile的內(nèi)存語(yǔ)義:嚴(yán)格限制編譯器和處理器對(duì)volatile變量與普通變量的重排序,確保volatile的寫(xiě)-讀和鎖的釋放-獲取具有相同的內(nèi)存語(yǔ)義。從編譯器重排序規(guī)則和處理器內(nèi)存屏障插入策略來(lái)看,只要volatile變量與普通變量之間的重排序可能會(huì)破壞volatile的內(nèi)存語(yǔ)義,這種重排序就會(huì)被編譯器重排序規(guī)則和處理器內(nèi)存屏障插入策略禁止。
當(dāng)線程釋放鎖時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存中。
當(dāng)線程獲取鎖時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無(wú)效。從而使得被監(jiān)視器保護(hù)的臨界區(qū)代碼必須從主內(nèi)存中讀取共享變量。
/** * */ class MonitorExample { int a = 0; public synchronized void writer() { // 1 a++; // 2 } // 3 public synchronized void reader() { // 4 int i = a; // 5 ...... } // 6 }
假設(shè)線程A執(zhí)行writer()方法,隨后線程B執(zhí)行reader()方法。根據(jù)happens-before規(guī)則,這個(gè)過(guò)程包含的happens-before關(guān)系可以分為3類(lèi)。
根據(jù)程序次序規(guī)則,1 happens-before 2,2 happens-before 3;4 happens-before 5,5 happens-before 6。
根據(jù)監(jiān)視器鎖規(guī)則,3 happens-before 4。
根據(jù)happens-before的傳遞性,2 happens-before 5。
鎖的釋放與獲取的內(nèi)存語(yǔ)義線程釋放鎖時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存中
線程獲取鎖時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無(wú)效。從而使得被監(jiān)視器保護(hù)的臨界區(qū)代碼必須從主內(nèi)存中讀取共享變量
final 域內(nèi)存語(yǔ)義final 域,編譯器與處理器要遵守兩個(gè)重排序規(guī)則
在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final域的寫(xiě)入,與隨后把這個(gè)被構(gòu)造對(duì)像的引用賦值給一個(gè)引用變量,這兩個(gè)操作之間不能重排序
初次讀一個(gè)包含final域的對(duì)象引用,與隨后初次讀這個(gè)final域,這兩個(gè)操作之間不能重排序(有點(diǎn)擾,eg:obj,obj.j的關(guān)系)
下面的示例代碼,說(shuō)明這兩個(gè)規(guī)則
/** * */ public class FinalExample{ int i; final int j; static FinalExample obj; static FinalExample(){ i = 1; j = 2; } public static void writer(){ obj = new FinalExample(); } public static void reader(){ FinalExample object = obj; int a = obj.i; int b = obj.j; } }寫(xiě)final域重排序規(guī)則
MM禁止編譯器把final域的寫(xiě)重排序到構(gòu)造函數(shù)之外。
JMM編譯器會(huì)在final域的寫(xiě)之后,構(gòu)造函數(shù)return之前,插入一個(gè)StoreStore屏障。這個(gè)屏障禁止處理器把final域的寫(xiě)重排序到構(gòu)造函數(shù)之外
讀final域重排序規(guī)則一個(gè)線程中,初次讀對(duì)象引用與初次讀該對(duì)象包含的final域,JMM禁止處理器重排序這兩個(gè)操作
分析上面代碼示例 reader()方法包含3個(gè)操作。
初次讀引用變量obj。
初次讀引用變量obj指向?qū)ο蟮钠胀ㄓ騤。
初次讀引用變量obj指向?qū)ο蟮膄inal域
final域?yàn)橐妙?lèi)型/** *假設(shè)首先線程A執(zhí)行writeOne方法,執(zhí)行完后線程B執(zhí)行writetwo()方法,執(zhí)行完后線程執(zhí)行reader()方法 *1是對(duì)final域的寫(xiě)入,2是對(duì)這個(gè)final域引用的對(duì)象的成員域的寫(xiě)入,3是把被構(gòu)造的對(duì)象的引用賦值給某個(gè)引用變量。這里除了前面提到的1不能和3重排序外,2和3也不能重排序 */ public class FinalReferenceExample { final int[] intArray; static FinalReferenceExample obj; public FinalReferenceExample () { intArray = new int[1]; //1 intArray[0] = 1; //2 } public static void writerOne () { //線程A obj = new FinalReferenceExample (); //3 } public static void writerTwo () { //線程B obj.intArray[0] = 2; //4 } public static void reader () { //線程C if (obj != null) { //5 int temp1 = obj.intArray[0]; //6 } }
本例final域?yàn)橐粋€(gè)引用類(lèi)型,它引用一個(gè)int型的數(shù)組對(duì)象。對(duì)于引用類(lèi)型,寫(xiě)final域的重排序規(guī)則對(duì)編譯器和處理器增加了如下約束:在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final引用的對(duì)象的成員域的寫(xiě)入,與隨后在構(gòu)造函數(shù)外把這個(gè)被構(gòu)造對(duì)象的引用賦值給一個(gè)引用變量,這兩個(gè)操作之間不能重排序。
final引用不能從構(gòu)造函數(shù)內(nèi)溢出/** * 步驟2使得構(gòu)造函數(shù)還未完成就對(duì)reader線程可見(jiàn) **/ public class FinalReferenceEscapeExample { final int i; static FinalReferenceEscapeExample obj; public FinalReferenceEscapeExample () { i = 1; // 1寫(xiě)final域 obj = this; // 2 this引用在此"逸出" } public static void writer() { new FinalReferenceEscapeExample (); } public static void reader() { if (obj != null) { // 3 int temp = obj.i; // 4 } }
結(jié)論:在構(gòu)造函數(shù)返回前,被構(gòu)造對(duì)象的引用不能為其他線程所見(jiàn)
雙重檢查鎖定與延遲初始化 java內(nèi)存模型綜述文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/68857.html
摘要:因?yàn)楣芾砣藛T是了解手下的人員以及自己負(fù)責(zé)的事情的。處理器優(yōu)化和指令重排上面提到在在和主存之間增加緩存,在多線程場(chǎng)景下會(huì)存在緩存一致性問(wèn)題。有沒(méi)有發(fā)現(xiàn),緩存一致性問(wèn)題其實(shí)就是可見(jiàn)性問(wèn)題。 網(wǎng)上有很多關(guān)于Java內(nèi)存模型的文章,在《深入理解Java虛擬機(jī)》和《Java并發(fā)編程的藝術(shù)》等書(shū)中也都有關(guān)于這個(gè)知識(shí)點(diǎn)的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說(shuō)自己更懵了。本文,就來(lái)整體的...
摘要:因?yàn)楣芾砣藛T是了解手下的人員以及自己負(fù)責(zé)的事情的。處理器優(yōu)化和指令重排上面提到在在和主存之間增加緩存,在多線程場(chǎng)景下會(huì)存在緩存一致性問(wèn)題。有沒(méi)有發(fā)現(xiàn),緩存一致性問(wèn)題其實(shí)就是可見(jiàn)性問(wèn)題。 網(wǎng)上有很多關(guān)于Java內(nèi)存模型的文章,在《深入理解Java虛擬機(jī)》和《Java并發(fā)編程的藝術(shù)》等書(shū)中也都有關(guān)于這個(gè)知識(shí)點(diǎn)的介紹。但是,很多人讀完之后還是搞不清楚,甚至有的人說(shuō)自己更懵了。本文,就來(lái)整體的...
摘要:編譯器,和處理器會(huì)共同確保單線程程序的執(zhí)行結(jié)果與該程序在順序一致性模型中的執(zhí)行結(jié)果相同。正確同步的多線程程序的執(zhí)行將具有順序一致性程序的執(zhí)行結(jié)果與該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同。 前情提要 深入理解Java內(nèi)存模型(六)——final 處理器內(nèi)存模型 順序一致性內(nèi)存模型是一個(gè)理論參考模型,JMM和處理器內(nèi)存模型在設(shè)計(jì)時(shí)通常會(huì)把順序一致性內(nèi)存模型作為參照。JMM和處理器內(nèi)...
摘要:內(nèi)存模型即,簡(jiǎn)稱(chēng),其規(guī)范了虛擬機(jī)與計(jì)算機(jī)內(nèi)存時(shí)如何協(xié)同工作的,規(guī)定了一個(gè)線程如何和何時(shí)看到其他線程修改過(guò)的值,以及在必須時(shí),如何同步訪問(wèn)共享變量。內(nèi)存模型要求調(diào)用棧和本地變量存放在線程棧上,對(duì)象存放在堆上。 Java內(nèi)存模型即Java Memory Model,簡(jiǎn)稱(chēng)JMM,其規(guī)范了Java虛擬機(jī)與計(jì)算機(jī)內(nèi)存時(shí)如何協(xié)同工作的,規(guī)定了一個(gè)線程如何和何時(shí)看到其他線程修改過(guò)的值,以及在必須時(shí),...
摘要:作為一個(gè)程序員,不了解內(nèi)存模型就不能寫(xiě)出能夠充分利用內(nèi)存的代碼。程序計(jì)數(shù)器是在電腦處理器中的一個(gè)寄存器,用來(lái)指示電腦下一步要運(yùn)行的指令序列。在虛擬機(jī)中,本地方法棧和虛擬機(jī)棧是共用同一塊內(nèi)存的,不做具體區(qū)分。 作為一個(gè) Java 程序員,不了解 Java 內(nèi)存模型就不能寫(xiě)出能夠充分利用內(nèi)存的代碼。本文通過(guò)對(duì) Java 內(nèi)存模型的介紹,讓讀者能夠了解 Java 的內(nèi)存的分配情況,適合 Ja...
閱讀 3558·2021-11-08 13:15
閱讀 2115·2019-08-30 14:20
閱讀 1396·2019-08-28 18:08
閱讀 989·2019-08-28 17:51
閱讀 1496·2019-08-26 18:26
閱讀 2998·2019-08-26 13:56
閱讀 1494·2019-08-26 11:46
閱讀 2594·2019-08-23 14:22