摘要:講什么內(nèi)存模型描述了多個線程之間通過內(nèi)存交互的規(guī)范,屏蔽了各種硬件和操作系統(tǒng)的訪問差異的,保證了程序在各種平臺下對內(nèi)存的訪問都能保證效果一致。這個版本的內(nèi)存模型在中仍然在使用。
JMM講什么
內(nèi)存模型(Memory Model)描述了多個線程之間通過內(nèi)存交互的規(guī)范,屏蔽了各種硬件和操作系統(tǒng)的訪問差異的,保證了Java程序在各種平臺下對內(nèi)存的訪問都能保證效果一致。在現(xiàn)代的多處理器(多核處理器)系統(tǒng)中,處理器擁有多級緩存以提升內(nèi)存訪問速度同時減少了內(nèi)存總線的訪問量。變量最終會保存在內(nèi)存中,但是編譯器、運行時、處理器可以對指令優(yōu)化和重新排序,緩存、寄存器也對內(nèi)存進行了讀寫優(yōu)化,只要保證在單個線程內(nèi)行為與代碼順序串行語義相同即可。內(nèi)存模型定義了充分且必要的條款,描述了程序中變量之間的關系,以及變量的讀取、寫入的底層細節(jié),實現(xiàn)了并發(fā)過程中的原子性、可見性、有序性。
老版本JMM中的問題原始的Java內(nèi)存模型存在一些不足,因此Java內(nèi)存模型在Java 1.5時被重新修訂(JSR133)。這個版本的Java內(nèi)存模型在Java 8中仍然在使用。老版本中的問題有:
final字段的值并不是完全不變的。構造器中對final字段值的寫入可以重排序至構造函數(shù)返回并將對象引用賦值給變量之后,導致其它線程看到還未完成初始化的final字段。這個問題的經(jīng)典案例是String的早期實現(xiàn)中,有多個final字段,但是其它線程可以看到字符串長度為0,而實際上字符串長度并不為0。
volatile字段的寫操作與非volatile字段的讀寫操作重排序。由于重排序的緣故,volatile字段的寫操作之前的操作被重新排序至之后進行,導致其它線程看到的結果與程序代碼不一致。這個問題的經(jīng)典案例是Double-Checked Locking(也稱multi-threaded singleton pattern)問題,即通過一個volatile字段來判斷是否需要進行初始化,從而實現(xiàn)延遲初始化并減少鎖操作的性能損耗。由于重排序的問題,導致部分初始化操作或構造操作被排序至volatile字段寫操作之后,導致其它線程看到部分初始化的數(shù)據(jù),破壞了數(shù)據(jù)的一致性。
JSR133解決的問題
volatile語義增強:volatile字段的讀、寫操作與其它任務內(nèi)存操作操作重排序,volatile的讀操作與監(jiān)視器鎖的獲取具有相同的內(nèi)存語義(緩存失效并從主存重新讀取),volatile的定操作與監(jiān)視器鎖的釋放具有相同的內(nèi)存語義(緩存刷入主存)。在這個約定下,線程A寫入volatile字段V后,線程B可以讀出V的值,同時線程A在寫入V時能夠看到的變量值對線程B也可見。
是否可以重排序 | 第二個操作 | 第二個操作 | 第二個操作 |
---|---|---|---|
第一個操作 | 普通讀/普通寫 | volatile讀/monitor enter | volatile寫/monitor exit |
普通讀/普通寫 | No | ||
voaltile讀/monitor enter | No | No | No |
volatile寫/monitor exit | No | No |
其中普通讀指getfield, getstatic, 非volatile數(shù)組的arrayload, 普通寫指putfield, putstatic, 非volatile數(shù)組的arraystore。volatile讀寫分別是volatile字段的getfield, getstatic和putfield, putstatic。monitorenter是進入同步塊或同步方法,monitorexist指退出同步塊或同步方法。
final字段增強:只要對象正確構造,那么不需要使用同步就可以保證任意線程都能看到final字段在構造器中被初始化之后的值。編譯器會在final域的寫之后,構造器函數(shù)return之前,插入StoreStore屏障。這個屏障禁止處理器把final域的寫重排序到構造函數(shù)之外。寫final域的重排序規(guī)則可以保證:在對象引用為任意線程可見之前,對象的final域已經(jīng)被正確初始化過了,而普通域不具有這個保障。讀final域的重排序規(guī)則是,禁止處理器重排序初次讀取對象引用與初次讀取該對象包含的final域這兩個操作。編譯器會在讀final域操作的前面插入一個LoadLoad屏障。讀final域的重排序規(guī)則可以確保:在讀一個對象的final域之前,一定會先讀包含這個final域的對象的引用。
初次讀對象引用與初次讀該對象包含的final域,這兩個操作之間存在間接依賴關系。由于編譯器遵守間接依賴關系,因此不會重排序這兩個操作;大多數(shù)處理器也會遵守間接依賴,也不會重排序這兩個操作。但有少數(shù)處理器允許對存在間接依賴關系的操作做重排序(比如alpha處理器),這個規(guī)則就是專門用來針對這種處理器的。
對于引用類型,寫final域的重排序規(guī)則對編譯器和處理器增加了如下約束:
在構造函數(shù)內(nèi)對一個final引用的對象的成員的寫入,與隨后在構造函數(shù)外把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序
確保在final引用的初始化在構造函數(shù)內(nèi)完成,因此一旦其他線程拿到了一個非null的final引用,那么這個引用一定是在構造函數(shù)內(nèi)被正確賦值的(至于是否正確初始化,則不一定,這取決于final引用的賦值語句)
Happens-Before規(guī)則HappendBefore規(guī)則包括:
程序順序規(guī)則: 如果程序中操作A在操作B之前,那么同一個線程中操作A將在操作B之前進行
監(jiān)視器鎖規(guī)則: 在監(jiān)視器鎖上的鎖操作必須在同一個監(jiān)視器鎖上的加鎖操作之前執(zhí)行
volatile變量規(guī)則: volatile變量的寫入操作必須在該變量的讀操作之前執(zhí)行
線程啟動規(guī)則: 在線程上對Thread.start的調(diào)用必須在該線程中執(zhí)行任何操作之前執(zhí)行
線程結束規(guī)則: 線程中的任何操作都必須在其他線程檢測到該線程已經(jīng)結束之前執(zhí)行
中斷規(guī)則: 當一個線程在另一個線程上調(diào)用interrupt時,必須在被中斷線程檢測到interrupt之前執(zhí)行
傳遞性: 如果操作A在操作B之前執(zhí)行,并且操作B在操作C之前執(zhí)行,那么操作A在操作C之前執(zhí)行
相關鏈接https://zhuanlan.zhihu.com/p/29881777
http://www.cs.umd.edu/~pugh/java/memoryModel/
https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf
http://gee.cs.oswego.edu/dl/jmm/cookbook.html
https://liuzhengyang.github.io/2017/05/12/javamemorymodel/
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/73961.html
摘要:編譯器,和處理器會共同確保單線程程序的執(zhí)行結果與該程序在順序一致性模型中的執(zhí)行結果相同。正確同步的多線程程序的執(zhí)行將具有順序一致性程序的執(zhí)行結果與該程序在順序一致性內(nèi)存模型中的執(zhí)行結果相同。 前情提要 深入理解Java內(nèi)存模型(六)——final 處理器內(nèi)存模型 順序一致性內(nèi)存模型是一個理論參考模型,JMM和處理器內(nèi)存模型在設計時通常會把順序一致性內(nèi)存模型作為參照。JMM和處理器內(nèi)...
摘要:前提深入理解內(nèi)存模型程曉明著,該書在以前看過一遍,現(xiàn)在學的東西越多,感覺那塊越重要,于是又再細看一遍,于是便有了下面的讀書筆記總結。同步同步是指程序用于控制不同線程之間操作發(fā)生相對順序的機制。線程之間的通信由內(nèi)存模型控制。 showImg(https://segmentfault.com/img/remote/1460000013474312?w=1920&h=1271); 前提 《深...
摘要:前提深入理解內(nèi)存模型程曉明著,該書在以前看過一遍,現(xiàn)在學的東西越多,感覺那塊越重要,于是又再細看一遍,于是便有了下面的讀書筆記總結。同步同步是指程序用于控制不同線程之間操作發(fā)生相對順序的機制。線程之間的通信由內(nèi)存模型控制。 showImg(https://mmbiz.qpic.cn/mmbiz_jpg/1flHOHZw6RtPu3BNx3zps1JhSmPICRw7QgeOmxOfTb...
摘要:內(nèi)存模型對內(nèi)存模型的介紹對內(nèi)存模型的結構圖的線程之間的通信是通過共享內(nèi)存的方式進行隱式通信,即線程把某狀態(tài)寫入主內(nèi)存中的共享變量,線程讀取的值,這樣就完成了通信。 Java內(nèi)存模型(JMM) 1.對內(nèi)存模型的介紹 ①對Java內(nèi)存模型的結構圖 java的線程之間的通信是通過共享內(nèi)存的方式進行隱式通信,即線程A把某狀態(tài)寫入主內(nèi)存中的共享變量X,線程B讀取X的值,這樣就完成了通信。是一種...
摘要:順序一致性內(nèi)存模型有兩大特性一個線程中所有操作必須按照程序的順序執(zhí)行。這里的同步包括對常用同步原語的正確使用通過以下程序說明與順序一致性兩種內(nèi)存模型的對比順序一致性模型中所有操作完全按程序的順序串行執(zhí)行。 java內(nèi)存模型 java內(nèi)存模型基礎 happen-before模型 JSR-133使用happen-before的概念來闡述操作之間的內(nèi)存可見性。在JMM中,如果一個操作執(zhí)行的結...
摘要:下面是該程序在兩個內(nèi)存模型中的執(zhí)行時序對比圖在順序一致性模型中,所有操作完全按程序的順序串行執(zhí)行。不保證未同步程序的執(zhí)行結果與該程序在順序一致性模型中的執(zhí)行結果一致。 前情提要 深入理解Java內(nèi)存模型(二)——重排序 數(shù)據(jù)競爭與順序一致性保證 當程序未正確同步時,就會存在數(shù)據(jù)競爭。java內(nèi)存模型規(guī)范對數(shù)據(jù)競爭的定義如下: 在一個線程中寫一個變量, 在另一個線程讀同一個變量,...
閱讀 2444·2021-10-09 09:44
閱讀 3817·2021-09-22 15:43
閱讀 2934·2021-09-02 09:47
閱讀 2556·2021-08-12 13:29
閱讀 3879·2019-08-30 15:43
閱讀 1689·2019-08-30 13:06
閱讀 2195·2019-08-29 16:07
閱讀 2756·2019-08-29 15:23