高并發(fā)編程底層原理詳解(1)

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)
image.png

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());
    }
image.png
image.png

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

image.png

image.png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容