并發(fā)編程之Java內(nèi)存模型順序一致性
簡(jiǎn)介:
順序一致性內(nèi)存模型是一個(gè)理論參考模型,處理器的內(nèi)存模型和編程語(yǔ)言的內(nèi)存模型都會(huì)以順序一致性內(nèi)存模型作為參照。
1、數(shù)據(jù)競(jìng)爭(zhēng)和順序一致性
當(dāng)程序未正確同步時(shí),就可能存在數(shù)據(jù)競(jìng)爭(zhēng)。
1.1 Java內(nèi)存模型規(guī)范對(duì)數(shù)據(jù)競(jìng)爭(zhēng)的定義
定義如下:
- 在一個(gè)線程中寫(xiě)一個(gè)變量
- 在另一個(gè)線程中讀同一個(gè)變量
- 寫(xiě)和讀沒(méi)有通過(guò)同步來(lái)排序
如果一個(gè)多線程程序能夠正確同步,這個(gè)程序?qū)⑹且粋€(gè)沒(méi)有數(shù)據(jù)競(jìng)爭(zhēng)的程序,往往存在數(shù)據(jù)競(jìng)爭(zhēng)的程序,運(yùn)行結(jié)果與我們的預(yù)期結(jié)果都會(huì)存在偏差。
1.2 JMM對(duì)多線程程序的內(nèi)存一致性做的保證
如果程序正確同步(正確使用synchronized
、volatile
和final
),程序的執(zhí)行將具有順序一致性(Sequentially Consistent
)——即程序的執(zhí)行結(jié)果與該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同。
2、順序一致性內(nèi)存模型
2.1 特性
- 一個(gè)線程中的所有操作必須按照程序的執(zhí)行順序來(lái)執(zhí)行
- (不管是否正確同步)所有的線程都只能看到一個(gè)單一的操作執(zhí)行順序,每個(gè)操作都必須原子執(zhí)行且立刻對(duì)所有線程可見(jiàn)。
圖示:順序一致性內(nèi)存模型視圖
在概念上,順序一致性模型有一個(gè)單一的全局內(nèi)存,這個(gè)內(nèi)存通過(guò)一個(gè)左右擺動(dòng)的開(kāi)關(guān)可以連接到任意一個(gè)線程,同時(shí)每一個(gè)線程必須按照程序的順序來(lái)執(zhí)行內(nèi)存的讀/寫(xiě)操作。上圖中可以看出,在任意時(shí)刻最多只有一個(gè)線程可以連接到內(nèi)存。因此,在多線程并發(fā)執(zhí)行時(shí),圖中的開(kāi)關(guān)裝置能把所有的內(nèi)存讀/寫(xiě)操作串行化(即在順序一致性模型中所有操作之間具有全序關(guān)系)。
2.2 舉例說(shuō)明順序一致性模型
假設(shè)兩個(gè)線程A和B并發(fā)執(zhí)行。其中
A線程的操作在程序中的順序?yàn)椋?/strong>A1 - A2 - A3
B線程的操作在程序中的順序?yàn)椋?/strong>B1 - B2 - B3
。
假設(shè)線程A和線程B使用監(jiān)視器鎖來(lái)正確同步,A線程的3個(gè)操作執(zhí)行后釋放監(jiān)視器鎖,隨后B線程獲取同一個(gè)監(jiān)視器鎖。那么程序在順序一致性模型中的執(zhí)行效果如下所示:順序一致性模型的一種執(zhí)行效果
假設(shè)線程A和線程B沒(méi)有做同步,那么這個(gè)未同步的程序在順序一致性模型中的另一種可能的效果如下所示:
順序一致性模型的另一種執(zhí)行效果:
未同步程序在順序一致性模型中雖然整體執(zhí)行順序是無(wú)序的,但是所有線程都只能看到一個(gè)一直的整體執(zhí)行順序。以上圖為例,線程A和B看到的執(zhí)行順序都是:A1 - B1 - A2 - B2 - A3 - B3
。之所以能得到這個(gè)保證是因?yàn)轫樞蛞恢滦詢?nèi)存模型中的每個(gè)操作必須立即對(duì)任意線程可見(jiàn)。
但是,在JMM中就沒(méi)有這個(gè)保證。未同步程序在JMM中不但整體的執(zhí)行順序是無(wú)序的,而且所有線程看到的操作執(zhí)行順序也可能不一致。 比如,在當(dāng)前線程把寫(xiě)過(guò)的數(shù)據(jù)緩存在本地內(nèi)存中,在沒(méi)有刷新到主內(nèi)存之前,這個(gè)寫(xiě)操作僅對(duì)當(dāng)前線程可見(jiàn);從其他線程的角度來(lái)觀察,會(huì)認(rèn)為這個(gè)寫(xiě)操作根本被當(dāng)前線程執(zhí)行。只有當(dāng)前線程把本地內(nèi)存中寫(xiě)過(guò)的數(shù)據(jù)刷新到主內(nèi)存之后,這個(gè)寫(xiě)操作才能對(duì)其他線程可見(jiàn)。這種情況就會(huì)出現(xiàn)多種運(yùn)行結(jié)果。
2.3 同步程序的順序一致性效果
對(duì)上一章的ReorderExample
程序用鎖來(lái)同步
package com.lizba.p1; /** * <p> * 同步示例 * </p> * * @Author: Liziba * @Date: 2021/6/8 21:44 */ public class SynReorderExample { // 定義變量a int a = 0; // flag變量是個(gè)標(biāo)記,用來(lái)標(biāo)志變量a是否被寫(xiě)入 boolean flag = false; public synchronized void writer() { // 獲取鎖 a = 1; flag = true; } // 釋放鎖 public synchronized void reader() { // 獲取鎖 if (flag) { int i = a * a; System.out.println("i:" + i); } } // 釋放鎖 }
測(cè)試代碼
/** * 測(cè)試 * * @param args */ public static void main(String[] args) { final SynReorderExample re = new SynReorderExample(); new Thread() { public void run() { re.writer(); } }.start(); new Thread() { public void run() { re.reader(); } }.start(); }
執(zhí)行多次結(jié)果結(jié)果都為1
總結(jié):
在上面的示例代碼中,假設(shè)A線程執(zhí)行
writer()
方法后,B線程執(zhí)行reader()
方法。這是一個(gè)正確同步的多線程程序。根據(jù)JMM規(guī)范,該程序的執(zhí)行結(jié)果將與該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同。
順序一致性模型中和JMM內(nèi)存模型中的執(zhí)行時(shí)序圖
總結(jié)
在順序一致性模型中,所有操作完全按程序的順序串行執(zhí)行。而在JMM中,臨界區(qū)內(nèi)的代碼可以重排序(但JMM不允許臨界區(qū)的代碼“逸出”到臨界區(qū)之外,那樣會(huì)破壞監(jiān)視器鎖的語(yǔ)義)。JMM會(huì)在進(jìn)入臨界區(qū)和退出臨界區(qū)的關(guān)鍵時(shí)間點(diǎn)做一些特殊處理,使得線程在這兩個(gè)時(shí)間點(diǎn)具有順序一致性模型中相同的內(nèi)存視圖。雖然線程A在臨界區(qū)內(nèi)做了重排序,但由于監(jiān)視鎖互斥執(zhí)行的特性,這里線程B無(wú)法“觀察”到線程A在臨界區(qū)內(nèi)的重排序。JMM在具體實(shí)現(xiàn)上的基本方針為:在不改變(正確同步)程序執(zhí)行結(jié)果的前提下,盡可能為編譯器和處理器的優(yōu)化打開(kāi)方便大門(mén)。
2.4 未同步程序的執(zhí)行特性
對(duì)于未同步或者未正確同步(代碼寫(xiě)錯(cuò)了的兄弟們),JMM只提供最小的安全性:
線程執(zhí)行時(shí)讀取到的值不會(huì)無(wú)中生有(Out Of Thin Air)
- 之前某個(gè)線程寫(xiě)入的值
- 默認(rèn)值(0、Null、False)-- JVM會(huì)在已經(jīng)清零了內(nèi)存空間(
Pre-zeroed Memory
)分配對(duì)象。
未同步程序在兩個(gè)模型中的執(zhí)行特性對(duì)比
比較內(nèi)容\模型名稱 | 順序一致性模型 | JMM模型 |
---|---|---|
單線程內(nèi)順序執(zhí)行 | √ | × |
一致的操作執(zhí)行順序 | √ | × |
64位long型和double型變量寫(xiě)原子性 | √ | × |
第三個(gè)差異和總線的機(jī)制有關(guān)。在一些32位處理器上,處理64位的數(shù)據(jù)寫(xiě)操作,需要將一個(gè)寫(xiě)操作拆分為兩個(gè)32位的寫(xiě)操作。
3、 64位long型和double型變量寫(xiě)原子性
3.1 CPU、內(nèi)存和總線簡(jiǎn)述
在計(jì)算機(jī)中,數(shù)據(jù)通過(guò)總線在處理器和內(nèi)存之間傳遞,每次處理器和內(nèi)存之間的數(shù)據(jù)傳遞都是通過(guò)一系列的步驟來(lái)完成的,這一系列的步驟稱之為總線事務(wù)(Bus Transaction
)??偩€事務(wù)包括讀事務(wù)(Read Transaction
)和寫(xiě)事務(wù)(WriteTransaction
),事務(wù)會(huì)讀\寫(xiě)內(nèi)存中一個(gè)或多個(gè)物理上連續(xù)的字。
- 讀事務(wù) → 內(nèi)存到處理器
- 寫(xiě)事務(wù) → 處理器到內(nèi)存
重點(diǎn):總線會(huì)同步試圖并發(fā)使用總線的事務(wù)。在一個(gè)處理器執(zhí)行總線事務(wù)期間,總線會(huì)禁止其他處理器和I\O設(shè)備執(zhí)行內(nèi)存的讀\寫(xiě)。
圖示:總線工作機(jī)制
由上圖所示:設(shè)處理器A、B、C、D同時(shí)向總線發(fā)起總線事務(wù),這時(shí)總線總裁(Bus Arbitration)會(huì)對(duì)競(jìng)爭(zhēng)作出裁決,這里假設(shè)處理器A在競(jìng)爭(zhēng)中獲勝(總線仲裁會(huì)確保所有處理器能公平訪問(wèn)內(nèi)存)。此時(shí)處理器A繼續(xù)它的總線事務(wù),而其他所有的總線事務(wù)必須要等待A的事務(wù)完成才能再次執(zhí)行內(nèi)存的讀\寫(xiě)操作??偩€事務(wù)工作機(jī)制確保處理器對(duì)內(nèi)存的訪問(wèn)以串行的方式執(zhí)行。在任意時(shí)間點(diǎn)都只有一個(gè)處理器可以訪問(wèn)內(nèi)存,這個(gè)特性能確??偩€事務(wù)之間的內(nèi)存讀\寫(xiě)操作具有原子性。
3.2 long和double類型的操作
在一些32位的處理器上,如果要求對(duì)64位數(shù)據(jù)的寫(xiě)操作具有原子性,那么會(huì)有非常大的同步開(kāi)銷。Java語(yǔ)言規(guī)范中鼓勵(lì)但不強(qiáng)求JVM對(duì)64位long
型和double
類型的變量寫(xiě)操作具有原子性。當(dāng)JVM在這種處理器上運(yùn)行時(shí),會(huì)把一個(gè)64位的變量寫(xiě)操作拆成兩個(gè)32位寫(xiě)操作來(lái)執(zhí)行,此時(shí)寫(xiě)不具備原子性。
圖示:總線事務(wù)執(zhí)行的時(shí)序圖
存在問(wèn)題:
假設(shè)處理器A寫(xiě)一個(gè)long類型的變量,同時(shí)處理器B要讀這個(gè)long類型的變量。處理器A中64位的寫(xiě)操作被拆分成兩個(gè)32位的寫(xiě)操作,且這兩個(gè)32位的寫(xiě)操作被分配到不同的事務(wù)中執(zhí)行。此時(shí),處理器B中64位的讀操作被分配到單個(gè)讀事務(wù)中執(zhí)行。如果按照上面的執(zhí)行順序,那么處理器B讀取的將會(huì)是一個(gè)不完整的無(wú)效值。
處理方式:
JSR-133內(nèi)存模型開(kāi)始(JDK1.5),寫(xiě)操作能拆分成兩個(gè)32位寫(xiě)事務(wù)執(zhí)行,讀操作必須在單個(gè)事務(wù)中執(zhí)行。
到此這篇關(guān)于并發(fā)編程之Java內(nèi)存模型順序一致性的文章就介紹到這了,更多相關(guān)Java內(nèi)存模型順序一致性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(6)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-07-07Java返回分頁(yè)結(jié)果集的封裝代碼實(shí)例
這篇文章主要介紹了java返回分頁(yè)結(jié)果集的封裝代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01通過(guò)weblogic API解析如何獲取weblogic中服務(wù)的IP和端口操作
這篇文章主要介紹了通過(guò)weblogic API解析如何獲取weblogic中服務(wù)的IP和端口操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06WebDriver實(shí)現(xiàn)自動(dòng)化打開(kāi)IE中的google網(wǎng)頁(yè)并實(shí)現(xiàn)搜索
這篇文章主要介紹了WebDriver實(shí)現(xiàn)自動(dòng)化打開(kāi)IE中的google網(wǎng)頁(yè)并實(shí)現(xiàn)搜索,需要的朋友可以參考下2014-04-04Java解決線程的不安全問(wèn)題之volatile關(guān)鍵字詳解
這篇文章主要介紹了Java解決線程的不安全問(wèn)題之volatile關(guān)鍵字詳解,可見(jiàn)性指一個(gè)線程對(duì)共享變量值的修改,能夠及時(shí)地被其他線程看到,而 volatile 關(guān)鍵字就保證內(nèi)存的可見(jiàn)性,需要的朋友可以參考下2023-08-08