日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java虚拟机10.内存模型与线程

發(fā)布時(shí)間:2023/12/10 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java虚拟机10.内存模型与线程 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
多任務(wù)處理在現(xiàn)代計(jì)算機(jī)操作系統(tǒng)中是一項(xiàng)必備的功能,讓計(jì)算機(jī)同時(shí)去做幾件事情,不僅是因?yàn)橛?jì)算機(jī)的運(yùn)算能力強(qiáng)大了,更重要的原因是計(jì)算機(jī)的運(yùn)算速度與它的存儲(chǔ)和通信子系統(tǒng)速度的差距太大,大量的時(shí)間都花費(fèi)在磁盤I/O,網(wǎng)絡(luò)通信或者數(shù)據(jù)庫訪問上,因此處理器在大部分時(shí)間里都處于等待其他資源的狀態(tài)。

如果讓計(jì)算機(jī)并發(fā)執(zhí)行若干個(gè)運(yùn)算任務(wù)就可以更充分地利用計(jì)算機(jī)處理器的效能,但是其中的復(fù)雜性是絕大多數(shù)的運(yùn)算任務(wù)都不可能只靠處理器計(jì)算就能完成,處理器至少要與內(nèi)存交互,如讀取運(yùn)算數(shù)據(jù)、存儲(chǔ)運(yùn)算結(jié)果等,這個(gè)I/O操作無法消除。

所以現(xiàn)代計(jì)算機(jī)通過加入一層讀寫速度盡可能接近處理器運(yùn)算速度的告訴緩存來作為內(nèi)存與處理器之間的緩沖:將運(yùn)算需要使用的數(shù)據(jù)復(fù)制到緩存中,讓運(yùn)算能快速進(jìn)行,當(dāng)運(yùn)算結(jié)束后再從緩存同步回內(nèi)存之中,這樣處理器就無需等待緩慢的內(nèi)存讀寫了。

基于高速緩存的存儲(chǔ)交互很好地解決了處理器與內(nèi)存的速度矛盾,但是引入了復(fù)雜度更高的一個(gè)問題:緩存一致性。

在多處理器系統(tǒng)中,每個(gè)處理器都有自己的高速緩存,而它們又共享同一主內(nèi)存。當(dāng)多個(gè)處理器的運(yùn)算任務(wù)都涉及同一塊主內(nèi)存區(qū)域時(shí),將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致,在將數(shù)據(jù)同步回主內(nèi)存時(shí)以誰的緩存數(shù)據(jù)為準(zhǔn)呢?為了解決一致性的問題,需要各個(gè)處理器訪問緩存時(shí)都遵循一些協(xié)議,在讀寫時(shí)要根據(jù)協(xié)議來進(jìn)行操作。

因此內(nèi)存模型可以理解為在特定的操作協(xié)議下,對(duì)特定的內(nèi)存或高速緩存進(jìn)行讀寫訪問的過程抽象。

除了增加高速緩存之外,為了使處理器內(nèi)部的運(yùn)算單元能盡量被充分利用,處理器可能會(huì)對(duì)輸入代碼進(jìn)行亂序執(zhí)行優(yōu)化,處理器會(huì)在計(jì)算之后將亂序執(zhí)行的結(jié)果重組,保證該結(jié)果與順序執(zhí)行的結(jié)果是一致的,但并不保證程序中的各個(gè)語句計(jì)算的先后順序與輸入的代碼順序一致。因此,如果存在一個(gè)計(jì)算任務(wù)依賴另外一個(gè)計(jì)算任務(wù)的中間結(jié)果,那么其順序性并不能依靠代碼的先后順序來保證。與處理器的亂序執(zhí)行優(yōu)化器類似,java虛擬機(jī)的即時(shí)編譯器中也有類似的指令重排序優(yōu)化。

Java內(nèi)存模型

java虛擬機(jī)規(guī)范中試圖定義一種java內(nèi)存模型來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問效果。

java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝,線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量。不同的線程之間也無法直接訪問對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過主內(nèi)存來完成。

內(nèi)存間交互操作

對(duì)于一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存的問題在java內(nèi)存模型中定義了8種操作:

1.lock:作用于主內(nèi)存的變量,它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)。

2.unlock:作用于主內(nèi)存的變量,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定。

3.read:作用于主內(nèi)存的變量,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動(dòng)作使用。

4.load:作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。

5.use:作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。

6.assign:作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作。

7.store:作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作使用。

8.write:作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值寫入主內(nèi)存的變量中。

如果要把一個(gè)變量從主內(nèi)存復(fù)制到工作內(nèi)存,需要順序的執(zhí)行read和load操作,如果要把變量從工作內(nèi)存同步回主內(nèi)存,需要順序執(zhí)行store和write。但java內(nèi)存模型只要求兩個(gè)操作時(shí)順序執(zhí)行,沒有保證是連續(xù)執(zhí)行,即在兩個(gè)操作之間可插入執(zhí)行其他指令。

所以java內(nèi)存模型還規(guī)定了在執(zhí)行上述8中操作時(shí)必須滿足的規(guī)則:

1.不允許read和load、store和write操作之一單獨(dú)出現(xiàn),即不允許一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接受,或者從工作內(nèi)存發(fā)起回寫了但主內(nèi)存不接受的情況。

2.不允許一個(gè)線程丟棄它的最近的assign操作,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存。

3.不允許一個(gè)線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中。

4.對(duì)一個(gè)變量實(shí)施use、store操作之前,必須先執(zhí)行過load、assign操作。

5.一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會(huì)被解鎖。

6.如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,那將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前,需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值。

7.如果一個(gè)變量事先沒有被lock操作鎖定,那就不允許對(duì)它執(zhí)行unlock操作,也不允許去unlock一個(gè)被其他線程鎖定的變量。

8.對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中,即store,write。

對(duì)于volatile型變量的特殊規(guī)則

java內(nèi)存模型對(duì)volatile型變量專門定義了一些特殊的訪問規(guī)則:

假定T表示一個(gè)線程,V表示volatile變量,那么在進(jìn)行read、load、use、assign、store和write操作時(shí)需要滿足如下規(guī)則:

1.只有當(dāng)線程T對(duì)變量V執(zhí)行的前一個(gè)動(dòng)作是load的時(shí)候,線程T才能對(duì)變量V執(zhí)行use動(dòng)作;并且只有當(dāng)線程T對(duì)變量V執(zhí)行的后一個(gè)動(dòng)作是use的時(shí)候,線程T才能對(duì)變量V執(zhí)行l(wèi)oad操作。線程T對(duì)變量V的use動(dòng)作可認(rèn)為是和線程T對(duì)變量V的load、read動(dòng)作相關(guān)聯(lián),必須連續(xù)一起出現(xiàn)。

這條規(guī)則要求在工作內(nèi)存中,每次使用V前都必須先從主內(nèi)存刷新最新的值,用于保證能看見其他線程對(duì)變量V所做的修改。

2.只有當(dāng)線程T對(duì)變量V執(zhí)行的前一個(gè)動(dòng)作是assign的時(shí)候,線程T才能對(duì)變量V執(zhí)行store動(dòng)作;并且,只有當(dāng)線程T對(duì)變量V執(zhí)行的后一個(gè)動(dòng)作是store的時(shí)候,線程T才能對(duì)變量V執(zhí)行assign動(dòng)作。線程T對(duì)變量V的assign動(dòng)作可認(rèn)為是和線程T對(duì)變量的store、write動(dòng)作相關(guān)聯(lián),必須連續(xù)一起出現(xiàn)。

這條規(guī)則要求在工作內(nèi)存中,每次修改V后都必須立即同步回主內(nèi)存中,用于保證其他線程可以看到自己對(duì)變量V的修改。

這兩個(gè)規(guī)則確保了volatile的可見性[1]。

所以volatile變量在各個(gè)線程的工作內(nèi)存中不存在一致性問題,但java里面的運(yùn)算并非原子操作,導(dǎo)致volatile變量在并發(fā)下一樣是不安全的(從主內(nèi)存獲取變量值到交給執(zhí)行引擎為止都是沒問題的,但是執(zhí)行引擎在執(zhí)行時(shí)不是原子操作,所以線程T1在修改變量值的時(shí)候,線程T2可以繼續(xù)獲取變量的舊值并交給自己的執(zhí)行引擎,這樣線程T1的修改對(duì)線程T2就不可見,即出現(xiàn)并發(fā)問題)。

由于volatile變量只能保證可見性,在不符合以下兩條規(guī)則的運(yùn)算場景中,仍然要通過加鎖來保證原子性。

1.運(yùn)算結(jié)果不依賴變量的當(dāng)前值,或者能夠確保只有單一的線程修改變量的值。

2.變量不需要與其他的狀態(tài)變量共同參與不變約束。

volatile變量的另一個(gè)語義是禁止指令重排序優(yōu)化[2]。

普通的變量僅僅會(huì)保證在該方法的執(zhí)行過程中所有依賴賦值結(jié)果的地方都能獲得正確的結(jié)果,而不能保證變量賦值操作的順序與程序代碼中的執(zhí)行順序一致。因?yàn)樵谝粋€(gè)線程的方法執(zhí)行過程中無法感知到這點(diǎn),也就java內(nèi)存模型中描述的:線程內(nèi)表現(xiàn)為串行的語義。

eg:

Map configOptions;

char[] configText;

volatile boolean initialized = false;

//假設(shè)以下代碼在線程A中執(zhí)行,讀取配置信息,讀取完后將initialized設(shè)置為true以通知其他線程配置可用。

configOptions = new HashMap();

configText = readConfigFile(fileName);

processConfigOptions(configText,configOptions);

initialized = true;

//假設(shè)以下代碼在線程B中執(zhí)行,等待initialized為true,代表線程A已經(jīng)把配置信息初始化完成。

while(!initialized){

?? sleep();

}

doSomethingWithConfig();

如果定義initialized變量時(shí)沒有使用volatile修飾,就可能由于指令重排序的優(yōu)化,導(dǎo)致位于線程A中最后一句的代碼"initialized=true"被提前執(zhí)行,這樣線程B中使用配置信息的代碼就可能出現(xiàn)錯(cuò)誤。

volatile變量相當(dāng)于在操作指令中的一個(gè)內(nèi)存屏障,在指令重排序時(shí)不能把后面的指令重排序到內(nèi)存屏障之前的位置。只有一個(gè)cpu訪問內(nèi)存時(shí),并不需要內(nèi)存屏障;但如果有多個(gè)cpu訪問同一塊內(nèi)存,且其中有一個(gè)在觀測另一個(gè),就需要內(nèi)存屏障來保證一致性了。

由于虛擬機(jī)對(duì)鎖實(shí)行的消除和優(yōu)化,使得我們很難量化地認(rèn)為volatile就會(huì)比synchronize快多少。volatile變量讀操作的性能消耗與普通變量幾乎沒有什么差別,但是寫操作則可能會(huì)慢一些,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行。不過在大多數(shù)場景下volatile的總開銷仍然要比鎖低。我們在volatile與鎖之間選擇的唯一依據(jù)僅僅是volatile的語義能否滿足使用場景的需求。

原子性、可見性、有序性

java內(nèi)存模型是圍繞著在并發(fā)過程中如何處理原子性、可見性和有序性這個(gè)三個(gè)特征來建立的。

原子性:

由java內(nèi)存模型來直接保證的原子性變量操作包括read、load、assign、use、store、和write,我們大致可以認(rèn)為基本數(shù)據(jù)類型的訪問讀寫是具備原子性的。對(duì)于更大范圍的原子性保證,java內(nèi)存模型提供了lock和unlock操作來滿足這種需求。

可見性:

java內(nèi)存模型是通過在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存作為傳遞介質(zhì)的方式來實(shí)現(xiàn)可見性的,無論是普通變量還是volatile變量都是如此,普通變量與volatile變量的區(qū)別是,volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存, 以及每次使用前立即從主內(nèi)存刷新。

除了volatile之外,java還有兩個(gè)關(guān)鍵字能實(shí)現(xiàn)可見性,即syncharonizedfinal

同步塊的可見性由"對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(store,write)"。而final的可見性是指,被final修飾的字段在構(gòu)造器中一旦初始化完成,并且構(gòu)造器沒有把"this"的引用傳遞出去,那在其他線程中就能看見final字段的值。

有序性:

java程序中天然的有序性可以總結(jié)為:如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程,所有的操作都是無序的。前半句指'線程內(nèi)表現(xiàn)為串行語義',后半句指'指令重排序和工作內(nèi)存與主內(nèi)存的同步延時(shí)。'

java提供了volatile和synchronized兩個(gè)關(guān)鍵字來保證線程之間操作的有序性,volatile本身就包含了禁止指令重排序的語義,而synchaonized則是有"一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作"(lock之后只有自己能unlock,將自己的結(jié)果同步到主內(nèi)存中)

先行發(fā)生規(guī)則

先行發(fā)生java內(nèi)存模型中定義的兩項(xiàng)操作之間的偏序關(guān)系,如果說操作A先行發(fā)生于操作B,其實(shí)就是說在發(fā)生操作B之前,操作A產(chǎn)生的影響能被B觀察到。

java內(nèi)存模型中有一些天然的先行發(fā)生關(guān)系,這些先行發(fā)生關(guān)系無須任何同步器協(xié)助就已經(jīng)存在。

程序次序規(guī)則? 在一個(gè)線程內(nèi),按照代碼順序(控制流),寫在前面的操作先行發(fā)生于寫在后面的操作。

管程鎖定規(guī)則? 一個(gè)unlock操作先行發(fā)生于后面(時(shí)間上的)對(duì)同一個(gè)鎖的lock操作,這里強(qiáng)調(diào)的是同一個(gè)鎖。

volatile變量規(guī)則? 對(duì)一個(gè)volatile變量的寫操作先行發(fā)生于后面(時(shí)間上的)對(duì)這個(gè)變量的讀操作。

線程啟動(dòng)規(guī)則? Thread對(duì)象的start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作。

線程終止規(guī)則? 線程中的所有操作都先行發(fā)生于對(duì)此線程的終止檢測,可以通過Thread.join()方法結(jié)束。

線程中斷規(guī)則? 對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生。

對(duì)象終結(jié)規(guī)則? 一個(gè)對(duì)象的初始化完成先行發(fā)生于它的finalize()方法的開始。

傳遞性? 如果操作A先行發(fā)生于操作B,操作B先行發(fā)生于操作C,那么操作A先行發(fā)生于操作C。

?

線程狀態(tài)

java定義了5種線程狀態(tài),在任意一個(gè)時(shí)間點(diǎn),一個(gè)線程有且只有其中一種狀態(tài)。

1.新建[New]:創(chuàng)建后尚未啟動(dòng)的線程處于這種狀態(tài)。

2.運(yùn)行[Runnable]:runnable包括了操作系統(tǒng)線程狀態(tài)中的Running和Ready,也就是處于此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待著CPU為它分配執(zhí)行時(shí)間。

3.無限期等待[Waiting]:處于這種狀態(tài)的線程不會(huì)被分配CPU執(zhí)行時(shí)間,他們要等待被其他線程顯示地喚醒:

.沒有設(shè)置Timeout參數(shù)的Object.wait()方法。

.沒有設(shè)置Timeout參數(shù)的Thread.join()方法。

.LockSupport.park()方法。

3.限期等待[Timed Waiting]:處于這種狀態(tài)的線程也不會(huì)被分配CPU執(zhí)行時(shí)間,不過無須等待被其他線程顯示地喚醒,在一定時(shí)間之后它們會(huì)由系統(tǒng)自動(dòng)喚醒:

.Thread.sleep()方法。

.設(shè)置了Timeout參數(shù)的Object.wait()方法。

.設(shè)置了Timeout參數(shù)的Thread.join()方法。

.LockSupport.parkNanos()方法。

.LockSupport.parkUntil()方法。

4.阻塞[blocked]:線程被阻塞了,“阻塞狀態(tài)”與“等待狀態(tài)”的區(qū)別是:“阻塞狀態(tài)”在等待著獲取到一個(gè)排他鎖,這個(gè)事件將在另外一個(gè)線程放棄這個(gè)鎖的時(shí)候發(fā)生,而“等待狀態(tài)”則是在等待一段時(shí)間,或者喚醒動(dòng)作的發(fā)生。在程序等待進(jìn)入同步區(qū)域的時(shí)候,線程將進(jìn)入這種狀態(tài)。

5.結(jié)束[Terminated]:已終止線程的線程狀態(tài),線程已經(jīng)結(jié)束執(zhí)行。

貼一張轉(zhuǎn)換圖:

?

Java語言中的線程安全

按照線程安全的“安全程度”由強(qiáng)至弱來排 序,可以將java語言中各種操作共享的數(shù)據(jù)分為以下5類:

不可變:只要一個(gè)不可變的對(duì)象被正確的構(gòu)建出來(沒有發(fā)生this引用逃逸的情況),那其外部的可見狀態(tài)永遠(yuǎn)也不會(huì)改變。String類就是一個(gè)典型的不可變對(duì)象,它的substring(),replace()和concat()方法都不會(huì)影響它原來的值,只會(huì)返回一個(gè)新構(gòu)造的字符串對(duì)象。保證對(duì)象行為不影響自己狀態(tài)的途徑有很多種,其中最簡單的就是把對(duì)象中帶有狀態(tài)的變量都聲明為final,這樣在構(gòu)造函數(shù)結(jié)束以后,它就是不可變的。 絕對(duì)線程安全:如vector容器,所有類方法都用synchronized修飾。 相對(duì)線程安全:通常意義上的線程安全,可以保證對(duì)象的單個(gè)操作是線程安全的,在調(diào)用的時(shí)候不需要做額外的保障措施,但對(duì)一些特定順序的連續(xù)調(diào)用,可能需要在調(diào)用端使用額外的同步手段來保證正確性。 線程兼容:指對(duì)象本身并不是線程安全的,但可以通過在調(diào)用端正確的使用同步手段來保證對(duì)象在并發(fā)環(huán)境中可以安全地使用。 線程對(duì)立:指無論調(diào)用端是否采取了同步措施,都無法再多線程環(huán)境中并發(fā)使用。

鎖消除

鎖消除是指虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí),對(duì)一些代碼上要求同步,但是被檢測到不可能存在共享數(shù)據(jù)競爭的鎖進(jìn)行消除。主要判斷依據(jù)來于逃逸分析的數(shù)據(jù)支持,如果判斷在一段代碼中,堆上的所有數(shù)據(jù)都不會(huì)逃逸出去從而被其他線程訪問到,那就可以把它們當(dāng)做棧上數(shù)據(jù)對(duì)待,認(rèn)為它們是線程私有的,同步加鎖自然就無須進(jìn)行。

例如:public String condat(String s1,String s2,String s3){

? ? ? ? ? ? return s1+s2+s3;

???????? }

在jdk1.5之前,會(huì)轉(zhuǎn)化為StringBuffer對(duì)象的連續(xù)append()操作,在jdk1.5及之后的版本中,會(huì)轉(zhuǎn)化為StringBuilder對(duì)象的連續(xù)append()操作。

其實(shí)每個(gè)StringBuffer.append()方法中都有一個(gè)同步塊,鎖就是stringbuffer對(duì)象,但虛擬機(jī)觀察stringbuffer對(duì)象,發(fā)現(xiàn)它的動(dòng)態(tài)作用域被限制在方法內(nèi)部,就是說不會(huì)逃逸到方法外,因此會(huì)將鎖消除掉。

?

#筆記內(nèi)容來自 《深入理解java虛擬機(jī)》

轉(zhuǎn)載于:https://www.cnblogs.com/shanhm1991/p/6403792.html

總結(jié)

以上是生活随笔為你收集整理的java虚拟机10.内存模型与线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。