CAS (compare and swap)比較并交換
在不加鎖的情況,保持在多線程的一致性問(wèn)題? => CAS
1.知識(shí)鋪墊:i++在jvm 中是不安全的,不能保證原子性,在高并發(fā)的情況下,會(huì)存在問(wèn)題。JMM(每個(gè)線程都會(huì)把主內(nèi)存的數(shù)據(jù)copy的線程自己的空間中,執(zhí)行完畢之后再寫(xiě)回主內(nèi)存。)
2.解決方式 a.加鎖 b.CAS
3.案列:AtomicInteger getAndAddIn()
4.流程解析:
i = 0
Thread A i++ ( 拿到i=0 ,并i = i+1 ,準(zhǔn)備存入)還未存入
Thread B 在Thread A拿到I=0 之后,先存入到主內(nèi)存中, 此時(shí) 主內(nèi)存中的值 已經(jīng)變?yōu)?
Thread A 此時(shí)發(fā)現(xiàn)主內(nèi)存已經(jīng)變?yōu)?了,于是,拿到i=1 ,重新執(zhí)行 i = i+1 ,并寫(xiě)入到主內(nèi)存中。(假設(shè)僅僅有2個(gè)線程。)
5.引發(fā)新問(wèn)題 ABA 問(wèn)題:
概念:線程a在最終寫(xiě)入主內(nèi)存時(shí),僅僅會(huì)比較當(dāng)前值,而不關(guān)注他的變化過(guò)程(他可能由 0—>1>0)
解決方案:加一個(gè)版本號(hào),樂(lè)觀鎖
//如果不相等的話,則繼續(xù)執(zhí)行一遍,再拿出值,進(jìn)行比較,直到相等 則寫(xiě)入。
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2); //native 實(shí)現(xiàn)。
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//native 實(shí)現(xiàn)。
return var5;
}
//引出匯編指令(也就是說(shuō)硬件直接支持)如果為多個(gè)cpu則加個(gè)lock
lock cmpxchg => compare exchange(lock保證原子性,其他cpu無(wú)法訪問(wèn))
volatile 和syn 都是通過(guò)它實(shí)現(xiàn)

java在內(nèi)存中的存儲(chǔ)布局
1.Object o = new Object()在內(nèi)存中占用多少個(gè)字節(jié)? => 16個(gè)字節(jié)(內(nèi)容為空,比如說(shuō):int 4個(gè)字節(jié),對(duì)象引用4個(gè)字節(jié))
2.對(duì)象頭 markword 8個(gè)字節(jié)(鎖信息、分帶年齡)(=>可以回顧一下垃圾回收過(guò)程:CMS 6 JVM 15 默認(rèn))
-- a.new -> 偏向鎖-> 輕量級(jí)鎖(無(wú)鎖、自旋鎖、自適應(yīng)鎖)->重量級(jí)鎖 (syn的優(yōu)化過(guò)程)
3.class 對(duì)象指針,指向class對(duì)象(默認(rèn)開(kāi)啟壓縮,被壓縮 成4個(gè)字節(jié),原8個(gè))
4.實(shí)例數(shù)據(jù):對(duì)象中存的數(shù)據(jù)、int 4個(gè)字節(jié),string 4個(gè)字節(jié)(壓縮后)
5.對(duì)齊(若不為8的倍數(shù),則自動(dòng)補(bǔ)齊,優(yōu)化方式,8字節(jié)更快)
//依賴(lài)
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
//測(cè)試代碼
public static void main(String[] args) {
Object obj = new Object();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}


Syn的優(yōu)化過(guò)程
前置知識(shí):由用戶態(tài) 向 內(nèi)核態(tài)申請(qǐng)重量鎖 ,非常耗費(fèi)資源。
1.無(wú)鎖
2.偏向鎖(存放線程指針、 偏向鎖標(biāo)記為1):線程執(zhí)行任務(wù)是很快的,一般也不會(huì)沖突,因此,默認(rèn)只需要加一個(gè)標(biāo)記,而不需要上鎖
3.輕量級(jí)鎖(自旋鎖,也就無(wú)鎖):釋放偏向鎖,記錄Lock Record (CAS )缺點(diǎn):耗費(fèi)CPU,適用于競(jìng)爭(zhēng)不太激烈的情況
4.重量級(jí)鎖,指向重量級(jí)鎖(mutex 互斥量)的指針,鎖標(biāo)記為10,當(dāng)競(jìng)爭(zhēng)加劇、或者自旋超過(guò) n次,則升級(jí),每一個(gè)重量級(jí)鎖都有一個(gè)隊(duì)列,沒(méi)被執(zhí)行的任務(wù)、是不會(huì)占用cpu的
5.GC標(biāo)記信息:CMS過(guò)程用到的標(biāo)記信息 鎖狀態(tài)為11
6:上鎖時(shí),hashCode 會(huì)被 存放到LR中。
鎖降級(jí)、鎖消除和鎖粗化
1.鎖降級(jí) -> GC的時(shí)候會(huì)發(fā)生
2.鎖消除,如果JVM發(fā)現(xiàn)某個(gè)局部變量、并不會(huì)被其他線程調(diào)用,則會(huì)把syn取消
3.鎖粗化,如果一直循環(huán)的在加鎖、釋放鎖,JVM會(huì)優(yōu)化成只在while中加一次鎖。
Syn的最底層實(shí)現(xiàn)
linux +HSDIS 做測(cè)試
HSDIS 反匯編工具 HotSpot Di assembly
·JIT Just In Time 及時(shí)編譯器 ,將熱點(diǎn)代碼直接編譯成機(jī)器語(yǔ)言,而不在重新編譯了。
最終會(huì)發(fā)現(xiàn)加了syn 或者使用了volatile變量 的方法 底層都是 lock cmpexh
Syn的實(shí)現(xiàn)過(guò)程
1.java代碼:synchronized 關(guān)鍵字
2.字節(jié)碼 monitorenter moniterexit
3.jvm 在執(zhí)行過(guò)程中自動(dòng)升級(jí)
4.硬件級(jí) 匯編語(yǔ)言 comxchg (compare and exchange)
Volatile
前置知識(shí):
計(jì)算機(jī)組成原理: 核心就是CPU+內(nèi)存+外設(shè)(硬盤(pán)、顯示器、網(wǎng)卡等等。)
執(zhí)行過(guò)程:內(nèi)存中存放的就是指令,CPU獲取、并執(zhí)行指令。
PC寄存器 是存放下一條指令的位置
ALU:計(jì)算
cache:多級(jí)緩存( 金字塔結(jié)構(gòu) 容量大、速度慢。 寄存器最快、硬盤(pán)最慢 (1:100w))
cache:就是緩存 用來(lái)提升訪問(wèn)速度的。
進(jìn)程和線程的區(qū)別:線程是CPU執(zhí)行的基本單位。進(jìn)程是CPU分配資源的基本單位。
線程切換:Context Switch 當(dāng)2線程進(jìn)行切換時(shí),需要將正在執(zhí)行的線程的數(shù)據(jù)進(jìn)行保存。再執(zhí)行第二個(gè)線程。
超線程:一個(gè)ALU對(duì)應(yīng)2組PC|Registers ,每個(gè)線程對(duì)應(yīng)一組 PC|Registers ,因此 多線程進(jìn)行切換時(shí),并不需要保存當(dāng)前線程的的數(shù)據(jù)。這就是所謂的四核八線程,因此線程切換的速度更快了。
緩存行 cache line:按行讀?。ǐ@取變量時(shí),會(huì)把相鄰數(shù)據(jù)一同獲取過(guò)來(lái),最近優(yōu)先原則)一行數(shù)據(jù)是64個(gè)字節(jié)。
緩存行越大,局部效率越高、但讀取時(shí)間慢、
緩存行越小、局部性效率低、但讀取時(shí)間快、(目前最優(yōu)選擇64字節(jié))
MESI (E(exclusive)、M(modified)、S(shared)、I(invalid))緩存一致性協(xié)議、不同的CPU用的協(xié)議都是不一樣的。(省略詳細(xì)過(guò)程)
基礎(chǔ)作用:
保證線程間的可見(jiàn)性。(每個(gè)線程都有自己的內(nèi)存空間,獲取變量時(shí),會(huì)復(fù)制一份到內(nèi)存中,當(dāng)主內(nèi)存中的值變化時(shí),會(huì)同步到各個(gè)線程中。)
禁止指令重排:
概念:CPU的亂序執(zhí)行,比如說(shuō) step1 需要耗費(fèi)100ms step2 需要10ms。則step2可能與step1并行。則step2先執(zhí)行完畢。(測(cè)試案列:略)
實(shí)現(xiàn)方式如下:
JVM:內(nèi)存屏障
匯編:lock指令
源自馬士兵2020年最新Java多線程高并發(fā)編程詳細(xì)講解公開(kāi)課:
https://www.bilibili.com/video/BV1xK4y1C7aT?p=2

