免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Java內(nèi)存模型

最近在看《深入理解Java虛擬機:JVM高級特性與最佳實踐》講到了線程相關(guān)的細節(jié)知識,里面講述了關(guān)于java內(nèi)存模型,也就是jsr 133定義的規(guī)范。

系統(tǒng)的看了jsr 133規(guī)范的前面幾個章節(jié)的內(nèi)容,覺得受益匪淺。廢話不說,簡要的介紹一下java內(nèi)存規(guī)范。

什么是內(nèi)存規(guī)范

在jsr-133中是這么定義的

A memory model describes, given a program and an execution trace of that program, whether
the execution trace is a legal execution of the program. For the Java programming language, the
memory model works by examining each read in an execution trace and checking that the write
observed by that read is valid according to certain rules.

也就是說一個內(nèi)存模型描述了一個給定的程序和和它的執(zhí)行路徑是否一個合法的執(zhí)行路徑。對于java序言來說,內(nèi)存模型通過考察在程序執(zhí)行路徑中每一個讀操作,根據(jù)特定的規(guī)則,檢查寫操作對應(yīng)的讀操作是否能是有效的。

java內(nèi)存模型只是定義了一個規(guī)范,具體的實現(xiàn)可以是根據(jù)實際情況自由實現(xiàn)的。但是實現(xiàn)要滿足java內(nèi)存模型定義的規(guī)范。

 

處理器和內(nèi)存的交互

這個要感謝硅工業(yè)的發(fā)展,導(dǎo)致目前處理器的性能越來越強大。目前市場上基本上都是多核處理器。如何利用多核處理器執(zhí)行程序的優(yōu)勢,使得程序性能得到極大的提升,是目前來說最重要的。

目前所有的運算都是處理器來執(zhí)行的,我們在大學(xué)的時候就學(xué)習(xí)過一個基本概念  程序 = 數(shù)據(jù) + 算法 ,那么處理器負責(zé)計算,數(shù)據(jù)從哪里獲取了?

數(shù)據(jù)可以存放在處理器寄存器里面(目前x86處理都是基于寄存器架構(gòu)的),處理器緩存里面,內(nèi)存,磁盤,光驅(qū)等。處理器訪問這些數(shù)據(jù)的速度從快到慢依次為:寄存器,處理器緩存,內(nèi)存,磁盤,光驅(qū)。為了加快程序運行速度,數(shù)據(jù)離處理器越近越好。但是寄存器,處理器緩存都是處理器私有數(shù)據(jù),只有內(nèi)存,磁盤,光驅(qū)才是才是所有處理器都可以訪問的全局數(shù)據(jù)(磁盤和光驅(qū)我們這里不討論,只討論內(nèi)存)如果程序是多線程的,那么不同的線程可能分配到不同的處理器來執(zhí)行,這些處理器需要把數(shù)據(jù)從主內(nèi)存加載到處理器緩存和寄存器里面才可以執(zhí)行(這個大學(xué)操作系統(tǒng)概念里面有介紹),數(shù)據(jù)執(zhí)行完成之后,在把執(zhí)行結(jié)果同步到主內(nèi)存。如果這些數(shù)據(jù)是所有線程共享的,那么就會發(fā)生同步問題。處理器需要解決何時同步主內(nèi)存數(shù)據(jù),以及處理執(zhí)行結(jié)果何時同步到主內(nèi)存,因為同一個處理器可能會先把數(shù)據(jù)放在處理器緩存里面,以便程序后續(xù)繼續(xù)對數(shù)據(jù)進行操作。所以對于內(nèi)存數(shù)據(jù),由于多處理器的情況,會變的很復(fù)雜。下面是一個例子:

 

初始值 a = b = 0

process1           process2

1:load a               5:load b

2:write a:2           6:add b:1

3:load b               7: load a

4:write b:1           8:write a:1

假設(shè)處理器1先加載內(nèi)存變量a,寫入a的值為2,然后加載b,寫入b的值為1,同時 處理2先加載b,執(zhí)行b+1,那么b在處理器2的結(jié)果可能是1 可能是3。因為在load b之前,不知道處理器1是否已經(jīng)吧b寫會到主內(nèi)存。對于a來說,假設(shè)處理器1后于處理器2把a寫會到主內(nèi)存,那么a的值則為2。

而內(nèi)存模型就是規(guī)定了一個規(guī)則,處理器如何同主內(nèi)存同步數(shù)據(jù)的一個規(guī)則。

內(nèi)存模型介紹

在介紹java內(nèi)存模型之前,我們先看看兩個內(nèi)存模型

Sequential Consistency Memory Model:連續(xù)一致性模型。這個模型定義了程序執(zhí)行的順序和代碼執(zhí)行的順序是一致的。也就是說 如果兩個線程,一個線程T1對共享變量A進行寫操作,另外一個線程T2對A進行讀操作。如果線程T1在時間上先于T2執(zhí)行,那么T2就可以看見T1修改之后的值。

這個內(nèi)存模型比較簡單,也比較直觀,比較符合現(xiàn)實世界的邏輯。但是這個模型定義比較嚴格,在多處理器并發(fā)執(zhí)行程序的時候,會嚴重的影響程序的性能。因為每次對共享變量的修改都要立刻同步會主內(nèi)存,不能把變量保存到處理器寄存器里面或者處理器緩存里面。導(dǎo)致頻繁的讀寫內(nèi)存影響性能。

 

Happens-Before Memory Model : 先行發(fā)生模型。這個模型理解起來就比較困難。先介紹一個現(xiàn)行發(fā)生關(guān)系 (Happens-Before Relationship

  如果有兩個操作A和B存在A Happens-Before B,那么操作A對變量的修改對操作B來說是可見的。這個現(xiàn)行并不是代碼執(zhí)行時間上的先后關(guān)系,而是保證執(zhí)行結(jié)果是順序的??聪旅胬觼碚f明現(xiàn)行發(fā)生

A,B為共享變量,r1,r2為局部變量初始 A=B=0Thread1   |	Thread21: r2=A   | 3: r1=B2: B=2    |  4: A=2 

  憑借直觀感覺,線程1先執(zhí)行 r2=A,則r2=0 ,然后賦值B=1,線程2執(zhí)行r1=B,由于線程1修改了B的值為1,所以r1=1。但是在現(xiàn)行發(fā)生內(nèi)存模型里面,有可能最終結(jié)果為r1 = r2 = 2。為什么會這樣,因為編譯器或者多處理器可能對指令進行亂序執(zhí)行,線程1 從代碼流上面看是先執(zhí)行r2 = A,B = 1,但是處理器執(zhí)行的時候會先執(zhí)行 B = 2 ,在執(zhí)行 r2 = A,線程2 可能先執(zhí)行 A = 2 ,在執(zhí)行r1 = B,這樣可能 會導(dǎo)致 r1 = r2 = 2。

那我們先看看先行發(fā)生關(guān)系的規(guī)則

  • 1 在同一個線程里面,按照代碼執(zhí)行的順序(也就是代碼語義的順序),前一個操作先于后面一個操作發(fā)生
  • 2 對一個monitor對象的解鎖操作先于后續(xù)對同一個monitor對象的鎖操作
  • 3 對volatile字段的寫操作先于后面的對此字段的讀操作
  • 4 對線程的start操作(調(diào)用線程對象的start()方法)先于這個線程的其他任何操作
  • 5 一個線程中所有的操作先于其他任何線程在此線程上調(diào)用 join()方法
  • 6 如果A操作優(yōu)先于B,B操作優(yōu)先于C,那么A操作優(yōu)先于C

解釋一下以上幾個先行發(fā)生規(guī)則的含義

規(guī)則1應(yīng)該比較好理解,因為比較適合人正常的思維。比如在同一個線程t里面,代碼的順序如下:

thread 1共享變量A、B局部變量r1、r2代碼順序1: A =12: r1 = A3: B = 24: r2 = B執(zhí)行結(jié)果 就是 A=1 ,B=2 ,r1=1 ,r2=2

因為以上是在同一個線程里面,按照規(guī)則1 也就是按照代碼順序,A = 1 先行發(fā)生 r1 =A ,那么r1 = 1

再看規(guī)則2,下面是jsr133的例子

按照規(guī)則2,由于unlock操作先于發(fā)生于lock操作,所以X=1對線程2里面就是可見的,所以r2 = 1

在分析以下,看這個例子,由于unlock操作先于lock操作,所以線程x=1對于線程2不一定是可見(不一定是現(xiàn)行發(fā)生的),所以r2的值不一定是1,有可能是x賦值為1之前的那個狀態(tài)值(假設(shè)x初始值為0,那么此時r2的值可能為0)

對于規(guī)則3,我們可以稍微修改一下我們說明的第一個例子

A,B為共享變量,并且B是valotile類型的r1,r2為局部變量初始 A=B=0Thread1   |	Thread21: r2=A   | 3: r1=B2: B=2    |  4: A=2 那么r1 = 2, r2可能為0或者2

 因為對于volatile類型的變量B,線程1對B的更新馬上線程2就是可見的,所以r1的值就是確定的。由于A是非valotile類型的,所以值不確定。

規(guī)則4,5,6這里就不解釋了,知道規(guī)則就可以了。

 可以從以上的看出,先行發(fā)生的規(guī)則有很大的靈活性,編譯器可以對指令進行重新排序,以便滿足處理器性能的需要。只要重新排序之后的結(jié)果,在單一線程里面執(zhí)行結(jié)果是可見的(也就是在同一個線程里面滿足先行發(fā)生原則1就可以了)。

java內(nèi)存模型是建立在先行發(fā)生的內(nèi)存模型之上的,并且再此基礎(chǔ)上,增強了一些。因為現(xiàn)行發(fā)生是一個弱約束的內(nèi)存模型,在多線程競爭訪問共享數(shù)據(jù)的時候,會導(dǎo)致不可預(yù)期的結(jié)果。有一些是java內(nèi)存模型可以接受的,有一些是java內(nèi)存模型不可以接受的。具體細節(jié)這里面就不詳細說明了。這里只說明關(guān)于java新的內(nèi)存模型重要點。

final字段的語義

在java里面,如果一個類定義了一個final屬性,那么這個屬性在初始化之后就不可以在改變。一般認為final字段是不變的。在java內(nèi)存模型里面,對final有一個特殊的處理。如果一個類C定義了一個非static的final屬性A,以及非static final屬性B,在C的構(gòu)造器里面對A,B進行初始化,如果一個線程T1創(chuàng)建了類C的一個對象co,同一時刻線程T2訪問co對象的A和B屬性,如果t2獲取到已經(jīng)構(gòu)造完成的co對象,那么屬性A的值是可以確定的,屬性B的值可能還未初始化,

下面一段代碼演示了這個情況

public class FinalVarClass {	public final int a ;	public int b = 0;		static FinalVarClass co;		public FinalVarClass(){		a = 1;		b = 1;	}		//線程1創(chuàng)建FinalVarClass對象 co	public static void create(){		if(co == null){			co = new FinalVarClass();		}	}		//線程2訪問co對象的a,b屬性	public static void vistor(){		if(co != null){			System.out.println(co.a);//這里返回的一定是1,a一定初始化完成			System.out.println(co.b);//這里返回的可能是0,因為b還未初始化完成		}	}}

為什么會發(fā)生這種情況,原因可能是處理器對創(chuàng)建對象的指令進行重新排序。正常情況下,對象創(chuàng)建語句co = new FinalVarClass()并不是原子的,簡單來說,可以分為幾個步驟,1 分配內(nèi)存空間 2 創(chuàng)建空的對象 3 初始化空的對象 4 把初始化完成的對象引用指向 co ,由于這幾個步驟處理器可能并發(fā)執(zhí)行,比如3,4 并發(fā)執(zhí)行,所以在create操作完成之后,co不一定馬上初始化完成,所以在vistor方法的時候,b的值可能還未初始化。但是如果是final字段,必須保證在對應(yīng)返回引用之前初始化完成。

volatile語義

對于volatile字段,在現(xiàn)行發(fā)生規(guī)則里面已經(jīng)介紹過,對volatile變量的寫操作先于對變量的讀操作。也就是說任何對volatile變量的修改,都可以在其他線程里面反應(yīng)出來。對于volatile變量的介紹可以參考 本人寫的一篇文章 《java中volatile關(guān)鍵字的含義》 里面有詳細的介紹。

volatile在java新的內(nèi)存規(guī)范里面還加強了新的語義。在老的內(nèi)存規(guī)范里面,volatile變量與非volatile變量的順序是可以重新排序的。舉個例子

public class VolatileClass {    int              x = 0;    volatile boolean v = false;    //線程1write     public void writer() {        x = 42;        v = true;    }    //線程2 read    public void reader() {        if (v == true) {            System.out.println(x);//結(jié)果可能為0,可能為2        }    }}

 線程1先調(diào)用writer方法,對x和v進行寫操作,線程reader判斷,如果v=true,則打印x。在老的內(nèi)存規(guī)范里面,可能對v和x賦值順序發(fā)生改變,導(dǎo)致v的寫操作先行于x的寫操作執(zhí)行,同時另外一個線程判斷v的結(jié)果,由于v的寫操作先行于v的讀操作,所以if(v==true)返回真,于是程序執(zhí)行打印x,此時x不一定先行與System.out.println指令之前。所以顯示的結(jié)果可能為0,不一定為2

但是java新的內(nèi)存模型jsr133修正了這個問題,對于volatile語義的變量,自動進行l(wèi)ock 和 unlock操作包圍對變量volatile的讀寫操作。那么以上語句的順序可以表示為

 

thread1              thread21 :write x=1        5:lock(m)2 :lock(m)          6:read v3 :write v=true     7:unlock(m)4 :unlock            8 :if(v==true)                     9: System.out.print(x)

 由于unlock操作先于lock操作,所以x寫操作5先于發(fā)生x的讀操作9

 

以上只是jsr規(guī)范中一些小結(jié)行的內(nèi)容,由于jsr133規(guī)范定義了很多術(shù)語以及很多推論,上述只是簡單的介紹了一些比較重要的內(nèi)容,具體細節(jié)可以參考jsr規(guī)范的public view :http://today.java.net/pub/a/today/2004/04/13/JSR133.html

 

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
深入理解Java內(nèi)存模型(一)——基礎(chǔ)
Java并發(fā)專題【3】Java內(nèi)存模型以及happens-before規(guī)則
深入理解Java虛擬機內(nèi)存模型
多線程并發(fā)支撐基礎(chǔ)之JAVA內(nèi)存模型
一篇文章總結(jié)了JVM線程基本原理
C++11 并發(fā)指南七(C++11 內(nèi)存模型一:介紹)
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服