1 什么是可見性?
- 通過
volatile修飾的變量被a線程修改b線程能立即讀取到修改后的值,不會出現(xiàn)'臟讀'
2 可見性原理
-
volatile修飾后hsdis多了個Lock匯編指令,Lock匯編指令是一種控制指令,作用是在多線程環(huán)境中,可以基于總線鎖或緩存鎖的機(jī)制來達(dá)到共享變量在線程間的可見性
3 硬件層面
- CPU>內(nèi)存>IO 硬件方面存在很大的處理速度的差異,木桶原理最---最短板決定整體性能
- 所以硬件方面的性能優(yōu)化要從兩方面著手:
①提高短板(基本不可實(shí)現(xiàn))
②最大化利用【性能過剩組件(CPU)】
3.1 最大化利用CPU方法

- CPU增加高速緩存,cpu絕大多數(shù)的業(yè)務(wù)處理中都會依賴內(nèi)存或者IO進(jìn)行運(yùn)算或數(shù)據(jù)存儲
- CPU告訴緩存通過降低內(nèi)存/IO讀取頻率來實(shí)現(xiàn)提高整體處理性能
- CPU高速緩存分為:L1>L2>L3三種,性能依次下降

- L1d:L1數(shù)據(jù)緩存
- L1i:L1指令緩存
- CPU高速緩存提高了CPU處理過程中頻繁與主內(nèi)存交互的性能
- CPU高速緩存也帶來了緩存(數(shù)據(jù))一致性的問題
3.2 緩存(數(shù)據(jù))一致性解決方案:
- 總線鎖
通過在總線添加鎖的方式來保證緩存(數(shù)據(jù))一致性,當(dāng)cup0通過總線操作數(shù)據(jù)時,其它c(diǎn)pu1將無法獲取總線的使用權(quán)限,對性能影響很大 - 緩存鎖
相對于總線鎖緩存鎖的范圍更加精確,降低看控制粒度,通過緩存一致性協(xié)議實(shí)現(xiàn) - 緩存一致性協(xié)議MESI
不同的CPU架構(gòu)里緩存一致性協(xié)議有著各自不同的實(shí)現(xiàn)方式,X86架構(gòu)中是基于MESI協(xié)議



M>Modified 修改狀態(tài)
E>Exclusive 獨(dú)享狀態(tài)
S>Shared 共享狀態(tài):表示數(shù)據(jù)可被多個緩存對象進(jìn)行緩存,且數(shù)據(jù)值與主內(nèi)存一致
I>Invlid 失效狀態(tài)
失效狀態(tài)緩存不可被使用,將從主內(nèi)存中進(jìn)行讀取
3.3 MESI的局限性
-
當(dāng)某個CPU修改緩存中的數(shù)據(jù)時,首先通知其cup緩存中的相同數(shù)據(jù),其它相同緩存置為失效
其它CPU緩存失效完成后再通知要修改的CPU,該過程中CUP處于阻塞中,浪費(fèi)了CPU性能
image.png
3.4 EMSI 改進(jìn)
-
為了減少緩存被修改過程中的阻塞時長,通知修改時采用異步操作,不進(jìn)行阻塞
將修改請求緩存到storebuffer中
image.png - storebuffer帶來的問題
value =3;
void cup0{
value = 10;// 通過storebuffer異步通知其他cpu緩存,將緩存value變?yōu)镮:失效狀態(tài)
isFinish = true; //E 獨(dú)占狀態(tài)
}
void cup1{
//由于cup0中storebuffer是異步操作
//所以理論上村 isFinish=true 而 value=3 這種情況
if(isFinish){//true
assert value == 10;//false
}
}
storebuffer可能會導(dǎo)致cup的亂序執(zhí)行既"指令重排序",重排序?qū)砜梢娦詥栴}
- 硬件層面的優(yōu)化,總是會帶來其他問題,無法真正解決可見性問題,所以cpu層面提供指令--內(nèi)存屏障供軟件方面調(diào)用
3.5 內(nèi)存屏障
value =3;
void cup0{
value = 10;// 通過storebuffer異步通知其他cpu緩存,將緩存value變?yōu)镮:失效狀態(tài)
加入內(nèi)存屏障
isFinish = true; //E 獨(dú)占狀態(tài)
}
void cup1{
//由于cup0中storebuffer是異步操作
//所以理論上村 isFinish=true 而 value=3 這種情況
if(isFinish){//true
讀取內(nèi)存屏障//由于cup0在 'sFinish = true; //E 獨(dú)占狀態(tài)' 前加入內(nèi)存屏障
//所以下面代碼中value值將,直接從主內(nèi)存中進(jìn)行獲取
assert value == 10;//false
}
}
cup層面提供了3中內(nèi)存屏障
讀屏障 store barrier
寫屏障 load barrier
全屏障 full barrierX86架構(gòu)中volatile關(guān)鍵字的實(shí)現(xiàn)依賴:volatile--->Lock指令(緩存鎖)--->內(nèi)存屏障
內(nèi)存屏障/指令重排序 等和平臺一級硬件有關(guān),不同硬件是不同的實(shí)現(xiàn).java是跨平臺語言,不需要在業(yè)務(wù)點(diǎn)中考慮硬件的差異性的是依托于JMM內(nèi)存模型的存在
4 JMM虛擬內(nèi)存模型

- 語言基本的抽象內(nèi)存模型,本與cpu內(nèi)存模型相類似
- 線程通過操作工作內(nèi)存來修改數(shù)據(jù),工作內(nèi)存負(fù)責(zé)和主內(nèi)存進(jìn)行通信和數(shù)據(jù)同步
- JMM虛擬內(nèi)存模型為作為一種標(biāo)準(zhǔn),不同的硬件設(shè)備有著各自的實(shí)現(xiàn)(指令).通過JMM業(yè)務(wù)代碼開發(fā)人員不需要關(guān)系硬件差異化,從而實(shí)現(xiàn)語言的跨平臺
4.1 重排序
- 代碼重排序順序:源代碼->編譯器重排序->CPU層面重排序(指令級、內(nèi)存)->最終執(zhí)行的指令
- 通過重排序可以提高代碼效率,但不是所用情況都會進(jìn)行重排序,是否重排序取決于【數(shù)據(jù)依賴規(guī)則】
/**
* 無數(shù)據(jù)依賴
* 1&2行代碼間無相互依賴
* 可進(jìn)行從排序
*/
int a = 1;
int b = 2;
/**
* 部分?jǐn)?shù)據(jù)依賴
* 1&2 行代碼間無數(shù)據(jù)依賴
* 1&3 行代碼間存在數(shù)據(jù)依賴
* 2&3 行代碼間存在數(shù)據(jù)依賴
* 1&2行可進(jìn)行重排序 1&3 2&3 行不可重排序
*/
int a=1;
int b = 2;
int c = a+b;
數(shù)據(jù)依賴規(guī)則:as-if-serial
無論代碼以何種方案進(jìn)行重排序,對于單個線程執(zhí)行代碼的結(jié)果不可變Happens-Befor
代碼A代碼的執(zhí)行結(jié)果對于B代碼必須是可見,就成為 A Happens-Befor B那些場景會觸發(fā)Happens-Before規(guī)則?
① 【程序的順序規(guī)則】
/**
* 單線程調(diào)用該方法時,A Happens-Befor B
**/
function X(){
a =1;// A
b =2;// A
}
② 【volatile規(guī)則】
被volatile修飾的變量寫操作一定對讀操作可見,即 "寫" Happens-Befor "讀"
③【傳遞性規(guī)則】
如果 :A Happens-Befor B & B Happens-Befor C
那么: A Happens-Befor C
④ 【start規(guī)則】
主線程里的start()方法 Happens-Befo 該線程run方法內(nèi)任意代碼
/**
* B Happens-Befor C (start規(guī)則)
* A Happens-Befor B (順序規(guī)則)
* A Happens-Befor C (傳遞性規(guī)則)
**/
public class A{
static x=0;
public static void main(String []args){
Thread t1=new Thread(()->{
//C .....
});
x=10;//A
t1.start();//B
}
}
⑤【Join規(guī)則】
線程run方法內(nèi)代碼 Happens-Befor join()后的代碼
public class Demo {
static int a = 0;
/**
* A Happens-Befor B
**/
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
a = 99;//A
});
t1.start();
t1.join();
System.out.println(a);//B
}
}
⑥ 【synchronized監(jiān)視器鎖規(guī)則】
synchronized 的占用順序決定線程代碼順序
public class Demo {
public void xx(){
synchronized (this){
//A...
}
}
public static void main(String[] args) {
/**
*t1 線程t1 代碼A Happens-Befor 線程t2 代碼A
*
**/
Demo demo = new Demo();
Thread t1 = new Thread(()-> demo.xx());
Thread t2 = new Thread(()-> demo.xx());
t1.start();
t1.join();//保證t1先于t2
t2.start();
}
}
- 無數(shù)據(jù)依賴情況下禁止重排序
當(dāng)代碼間不存在數(shù)據(jù)依賴,但在多線程調(diào)用的場景下可能會導(dǎo)致執(zhí)行結(jié)果錯誤,此時需要人工干預(yù)重排序---JMM內(nèi)存屏障
value =3;
void cup0{
value = 10;// 通過storebuffer異步通知其他cpu緩存,將緩存value變?yōu)镮:失效狀態(tài)
isFinish = true; //E 獨(dú)占狀態(tài)
}
void cup1{
if(isFinish){//true
assert value == 10;//false
}
}
- JMM內(nèi)存屏障:編譯器級別內(nèi)存屏障、CPU級別內(nèi)存屏障
4.2 JMM解決有序性、可見性方案
- volatile
可解決可見性。通過內(nèi)存屏障實(shí)現(xiàn) - synchronized
可解決可見性、有序性、原子性。通過對線程阻塞實(shí)現(xiàn)單線程調(diào)用來實(shí)現(xiàn) - final
遍歷不可變,避免了可見性、原子性等問題 - happens-before
5 線程的順序執(zhí)行
- 使用join
阻塞主線程,直到調(diào)用join()方法的線程執(zhí)行完畢;或者說調(diào)用join()線程的執(zhí)行結(jié)果對主線程可見,底層通過wait/notify實(shí)現(xiàn)
/**
* 只有添加join后線程才會123依次執(zhí)行
**/
Thread t1 = new Thread(()->{
//doSomething1
});
Thread t2 = new Thread(()->{
//doSomething2
});
Thread t3 = new Thread(()->{
//doSomething3
});
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();

