什么?面试官问我Java内存模型!这不得给我加薪?
內(nèi)存模型的基礎(chǔ)
- 通信 線(xiàn)程之間以何種機(jī)制來(lái)交換信息
- 共享內(nèi)存 隱式通信
- 消息傳遞 顯示通信
- 同步 程序中用于控制不同線(xiàn)程間操作,發(fā)生的相對(duì)順序的機(jī)制
- 共享內(nèi)存 顯式同步
- 消息傳遞 隱式同步
Java線(xiàn)程線(xiàn)程之間是通過(guò)共享內(nèi)存的方式實(shí)現(xiàn)通信的.
內(nèi)存模型的抽象結(jié)構(gòu)
- 共享變量
共享變量手內(nèi)存模型影響,線(xiàn)程會(huì)去主內(nèi)存里去加載共享變量,當(dāng)線(xiàn)程需要改變共享變量時(shí),會(huì)將本地內(nèi)存已更改的副本提交到主內(nèi)存.
- 局部變量
局部變量不會(huì)受內(nèi)存模型的影響
線(xiàn)程之間通信
指令重排
- 編譯器優(yōu)化的重排序
- 指令級(jí)并行的重排序
- 內(nèi)存系統(tǒng)的重排序
什么是指令重排?
int i=0; 2 int j=1;
按照我們的認(rèn)知,程序是一行一行往下執(zhí)行的,但是由于編譯器或運(yùn)行時(shí)環(huán)境為了優(yōu)化程序性能,采取對(duì)指令進(jìn)行重新排序執(zhí)行,也就是說(shuō)在計(jì)算機(jī)執(zhí)行上面兩句話(huà)的時(shí)候,有可能第二條語(yǔ)句會(huì)優(yōu)先于第一條語(yǔ)句執(zhí)行.
然而并不是所有的指令都能重排,重排需要基于數(shù)據(jù)依賴(lài)性.
數(shù)據(jù)依賴(lài)性
如果兩個(gè)操作訪(fǎng)問(wèn)同一個(gè)變量,且這兩個(gè)操作中有一個(gè)為寫(xiě)操作,此時(shí)這兩個(gè)操作之間就存在數(shù)據(jù)依賴(lài)性。數(shù)據(jù)依賴(lài)分下列三種類(lèi)型:
| 寫(xiě)后讀 | a=1;b=a; | 寫(xiě)一個(gè)變量之后,再讀這個(gè)位置. |
| 寫(xiě)后寫(xiě) | a=1;a=2; | 寫(xiě)一個(gè)變量之后,再寫(xiě)這個(gè)變量. |
| 讀后寫(xiě) | a=b;b=1; | 讀一個(gè)變量之后,再寫(xiě)這個(gè)變量. |
上面的情況,如果重排序了兩個(gè)操作的執(zhí)行順序,程序的執(zhí)行結(jié)果將會(huì)跟預(yù)期完全不一樣.
所以說(shuō),雖然編譯器和處理器可能會(huì)對(duì)操作做重排序,但是編譯器和處理器在重排序時(shí),會(huì)遵守?cái)?shù)據(jù)依賴(lài)性,編譯器和處理器不會(huì)改變存在數(shù)據(jù)依賴(lài)關(guān)系的兩個(gè)操作的執(zhí)行順序。
注意,這里所說(shuō)的數(shù)據(jù)依賴(lài)性?xún)H針對(duì)單個(gè)處理器中執(zhí)行的指令序列和單個(gè)線(xiàn)程中執(zhí)行的操作,不同處理器之間和不同線(xiàn)程之間的數(shù)據(jù)依賴(lài)性不被編譯器和處理器考慮。
as-if-serial
定義:不管怎么重排序(編譯器和處理器為了提?并?度),(單線(xiàn)程) 程序的執(zhí)?結(jié)果不能被改變。編譯器、runtime和處理器都必須遵守as-if-serial語(yǔ)義。
happens-before
happens-before是JMM的最核心概念之一
JMM設(shè)計(jì)意圖
- 程序員對(duì)內(nèi)存模型的使用
- 為程序員提供足夠強(qiáng)的內(nèi)存可見(jiàn)性保證
- 編譯器和處理器對(duì)內(nèi)存模型的實(shí)現(xiàn)
- 對(duì)編譯器和處理器的限制要盡可能的放松
JMM禁止:
禁止編譯器和處理器會(huì)改變程序執(zhí)行結(jié)果的重排序.
JMM允許:
允許編譯器和處理器不會(huì)改變程序執(zhí)行結(jié)果的重排序.
happens-before規(guī)則
在JMM中,如果?個(gè)操作執(zhí)?的結(jié)果需要對(duì)另?個(gè)操作可?,那么這兩個(gè)操作之間必須要存在happens-before關(guān)系.
- 程序順序規(guī)則 ?個(gè)線(xiàn)程中的每個(gè)操作,happens-before于該線(xiàn)程中的任意后續(xù)操作.
- 監(jiān)視器鎖規(guī)則 對(duì)?個(gè)鎖的解鎖,happens-before于隨后對(duì)這個(gè)鎖的加鎖.
- volatile變量規(guī)則 對(duì)?個(gè)volatile域的寫(xiě),happens-before于任意后續(xù)對(duì)這個(gè)volatile域的讀.
- 傳遞性 如果A happens-before B,且B happens-before C,那么A happens-before C.
- start()規(guī)則 如果線(xiàn)程A執(zhí)?操作ThreadB.start()(啟動(dòng)線(xiàn)程B),那么A線(xiàn)程的ThreadB.start()操作happens-before于線(xiàn)程B中的任意操作。
- join()規(guī)則 如果線(xiàn)程A執(zhí)?操作ThreadB.join()并成功返回,那么線(xiàn)程B中的任意操作happens-before于線(xiàn)程A從ThreadB.join()返回
- 線(xiàn)程中斷規(guī)則 對(duì)線(xiàn)程interrupt?法的調(diào)?happens-before于被中斷線(xiàn)程的代碼檢測(cè)到中斷事件的發(fā)?.
- 對(duì)象終結(jié)規(guī)則 ?個(gè)對(duì)象的初始化的完成,也就是構(gòu)造函數(shù)執(zhí)?的結(jié)束?定 happens-before它的finalize()?法.
JMM向程序員提供的happens-before規(guī)則能滿(mǎn)?程序員的需求.
JMM對(duì)編譯器和處理器的束縛已經(jīng)盡可能少.
JMM對(duì)程序員的承諾
如果?個(gè)操作happens-before另?個(gè)操作,那么第?個(gè)操作的執(zhí)?結(jié)果將對(duì)第?個(gè)操作 可?,?且第?個(gè)操作的執(zhí)?順序排在第?個(gè)操作之前.
JMM對(duì)編譯器和處理器重排序的約束原則
兩個(gè)操作之間存在happens-before關(guān)系,并不意味著Java平臺(tái)的具體實(shí)現(xiàn)必須要按照 happens-before關(guān)系指定的順序來(lái)執(zhí)?.
例子:
1 public class Demo29 { 2 int a=0; 3 boolean flag=false; 4 public void writer(){ 5 a=1; //1 6 flag=true; //2 7 } 8 public void reader(){ 9 if(flag){ //3 10 int i=a * a; //4 11 } 12 } 13 }假如線(xiàn)程B在進(jìn)行操作4時(shí),能否看到線(xiàn)程A在操作1對(duì)共享變量a的寫(xiě)入呢? 不一定
| 時(shí)刻 | 線(xiàn)程A | 線(xiàn)程B | | T1 | flag=true | | | T2 | | if(flag) | | T3 | | int i=a*a | | T4 | a=1 | |當(dāng)線(xiàn)程A在執(zhí)行writer方法時(shí),因?yàn)橹噶钪嘏判?會(huì)先執(zhí)行flag=true,再執(zhí)行a=1.而線(xiàn)程B在執(zhí)行操作4時(shí)就會(huì)讀不到線(xiàn)程A對(duì)共享變量a的寫(xiě)入,導(dǎo)致運(yùn)行結(jié)果超出預(yù)期.
解決方案1:
通過(guò)加鎖的方式來(lái)解決
1 public class Demo29 { 2 int a=0; 3 boolean flag=false; 4 public synchronized void writer(){ 5 a=1; //1 6 flag=true; //2 7 } 8 public synchronized void reader(){ 9 if(flag){ //3 10 int i=a * a; //4 11 } 12 } 13 }鎖的內(nèi)存語(yǔ)義:
- 線(xiàn)程A釋放?個(gè)鎖,實(shí)質(zhì)上是線(xiàn)程A向接下來(lái)將要獲取這個(gè)鎖的某個(gè)線(xiàn)程發(fā)出了(線(xiàn)程A 對(duì)共享變量所做修改的)消息。
- 線(xiàn)程B獲取?個(gè)鎖,實(shí)質(zhì)上是線(xiàn)程B接收了之前某個(gè)線(xiàn)程發(fā)出的(在釋放這個(gè)鎖之前對(duì)共 享變量所做修改的)消息。
- 線(xiàn)程A釋放鎖,隨后線(xiàn)程B獲取這個(gè)鎖,這個(gè)過(guò)程實(shí)質(zhì)上是線(xiàn)程A通過(guò)主內(nèi)存向線(xiàn)程B發(fā) 送消息。
volatile的作用
volatile內(nèi)存語(yǔ)義
- 線(xiàn)程A寫(xiě)?個(gè)volatile變量,實(shí)質(zhì)上是線(xiàn)程A向接下來(lái)將要讀這個(gè)volatile變量的某個(gè)線(xiàn)程 發(fā)出了(其對(duì)共享變量所做修改的)消息。
- 線(xiàn)程B讀?個(gè)volatile變量,實(shí)質(zhì)上是線(xiàn)程B接收了之前某個(gè)線(xiàn)程發(fā)出的(在寫(xiě)這個(gè)volatile 變量之前對(duì)共享變量所做修改的)消息。
- 線(xiàn)程A寫(xiě)?個(gè)volatile變量,隨后線(xiàn)程B讀這個(gè)volatile變量,這個(gè)過(guò)程實(shí)質(zhì)上是線(xiàn)程A通過(guò) 主內(nèi)存向線(xiàn)程B發(fā)送消息。
volatile內(nèi)存語(yǔ)義的實(shí)現(xiàn)
| 第一個(gè)操作 | 普通讀/寫(xiě) |
| 普通讀/寫(xiě) | Y |
| volatile讀 | N |
| volatile寫(xiě) | Y |
-
當(dāng)?shù)?個(gè)操作是volatile寫(xiě)時(shí),不管第?個(gè)操作是什么,都不能重排序。
-
當(dāng)?shù)?個(gè)操作是volatile讀時(shí),不管第?個(gè)操作是什么,都不能重排序。
-
當(dāng)?shù)?個(gè)操作是volatile寫(xiě),第?個(gè)操作是volatile讀時(shí),不能重排序。
內(nèi)存屏障
| LoadLoad Barriers | Load1;LoadLoad;Load2 | 確保Load1數(shù)據(jù)的裝載先于Load2及所有后續(xù)裝載指令的裝載 |
| StoreStore Barriers | Store1;StoreStore;Store2 | 確保Store1數(shù)據(jù)對(duì)其他處理器可見(jiàn)(刷新達(dá)到內(nèi)存)先于Store2及所有后續(xù)存儲(chǔ)指令的存儲(chǔ) |
| LoadStore Barriers | Load1;LoadStrore;Store2 | 確保Load1數(shù)據(jù)裝載先于Store2及所有后續(xù)的存儲(chǔ)指令刷新到內(nèi)存 |
| StoreLoad Barriers | Store;StoreLoad;Load2 | 確保Store1數(shù)據(jù)對(duì)其他處理器變得可見(jiàn)(指刷新到內(nèi)存)先于Load2及所有后續(xù)裝載指令的裝載.StoreLoad Barriers會(huì)使該屏障之前的所有內(nèi)存訪(fǎng)問(wèn)指令(存儲(chǔ)和裝載指令)完成之后,才執(zhí)行該屏障之后的內(nèi)存訪(fǎng)問(wèn)指令 |
- 在每個(gè)volatile寫(xiě)操作的前?插??個(gè)StoreStore屏障
- 在每個(gè)volatile寫(xiě)操作的后?插??個(gè)StoreLoad屏障
- 在每個(gè)volatile讀操作的后?插??個(gè)LoadLoad屏障
- 在每個(gè)volatile讀操作的后?插??個(gè)LoadStore屏障
Final的內(nèi)存語(yǔ)義
寫(xiě)final域的重排序規(guī)則
- JMM禁止編譯器把final域的寫(xiě)重排序到構(gòu)造函數(shù)之外.
- 編譯器會(huì)在final域的寫(xiě)之后,構(gòu)造函數(shù)return之前插入一個(gè)StoreStore屏障
讀final域的重排序規(guī)則
- 在?個(gè)線(xiàn)程中,初次讀對(duì)象引?與初次讀該對(duì)象包含的final域,JMM禁?處理器重排序這兩個(gè)操作
- 在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final引用的對(duì)象的成員域的寫(xiě)入,與隨后在構(gòu)造函數(shù)外把這個(gè)被構(gòu)造對(duì)象的引用賦值給一個(gè)引用變量,這兩個(gè)操作之間不能重排序
寫(xiě)final域的重排序規(guī)則
- 在構(gòu)造函數(shù)內(nèi)對(duì)?個(gè)final引?的對(duì)象的成員域 的寫(xiě)?,與隨后在構(gòu)造函數(shù)外把這個(gè)被構(gòu)造對(duì)象的引?賦值給?個(gè)引?變量,這兩個(gè)操作之 間不能重排序。
多線(xiàn)程下的單例模式
雙重檢查鎖定
1 public class DoubleCheckedLocking {2 private static DoubleCheckedLocking doubleCheckedLocking;3 4 private DoubleCheckedLocking() {5 6 }7 8 public static DoubleCheckedLocking getInstance() { 9 if (doubleCheckedLocking == null) { 10 synchronized (DoubleCheckedLocking.class) { 11 if (doubleCheckedLocking == null) { 12 doubleCheckedLocking = new DoubleCheckedLocking();//問(wèn)題出現(xiàn)在這里 13 } 14 } 15 } 16 return doubleCheckedLocking; 17 } 18 }我們來(lái)看看這段雙重檢查鎖定的單例模式有什么問(wèn)題?
線(xiàn)程A設(shè)置指向剛分配的內(nèi)存地址后,線(xiàn)程B就判斷doubleCheckedLocking對(duì)象是否為空,然后直接返回未初始化的doubleCheckedLocking對(duì)象,這樣會(huì)引發(fā)出很?chē)?yán)重的問(wèn)題.
解決方案1:
使用volatile,禁止2和3重排序
1 public class DoubleCheckedLocking { 2 private volatile static DoubleCheckedLocking doubleCheckedLocking; 3 4 private DoubleCheckedLocking() { 5 6 } 7 8 public static DoubleCheckedLocking getInstance() { 9 if (doubleCheckedLocking == null) { 10 synchronized (DoubleCheckedLocking.class) { 11 if (doubleCheckedLocking == null) { 12 doubleCheckedLocking = new DoubleCheckedLocking();//問(wèn)題出現(xiàn)在這里 13 } 14 } 15 } 16 return doubleCheckedLocking; 17 } 18 }解決方案2:
基于類(lèi)初始化,允許2和3重排序,但不允許其他線(xiàn)程"看到這個(gè)重排序"
1 public class InstanceFactory { 2 private static class InstanceHolder { 3 public static DoubleCheckedLocking doubleCheckedLocking = new DoubleCheckedLocking(); 4 } 5 6 public static DoubleCheckedLocking getInstance() { 7 return InstanceHolder.doubleCheckedLocking; 8 } 9 }這里使用到了靜態(tài)內(nèi)部類(lèi)的靜態(tài)屬性,類(lèi)的靜態(tài)屬性只會(huì)在第一次調(diào)用的時(shí)候初始化,而且會(huì)有一個(gè)Class對(duì)象的初始化鎖,從而確保只會(huì)發(fā)生一次初始化.
最后,祝大家早日學(xué)有所成,拿到滿(mǎn)意offer
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的什么?面试官问我Java内存模型!这不得给我加薪?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 初级Java开发面试必问项!!! 标识符
- 下一篇: 项目经理问我Java内存区域模型!急急急