免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
終于有大佬把'計(jì)算機(jī)底層原理'全部總結(jié)出來(lái)了

計(jì)算機(jī)的歷史

算盤和機(jī)械計(jì)算機(jī)

有很多民族自豪感爆棚的兄弟會(huì)把算盤當(dāng)成計(jì)算機(jī)的起源,還有爆破天的兄弟會(huì)把陰陽(yáng)當(dāng)成二進(jìn)制0和1的起源,我覺得這件事兒就有點(diǎn)兒不靠譜了

如果非要追究計(jì)算機(jī)的鼻祖,那就得講講17世紀(jì)前歐洲的故事,最早的計(jì)算機(jī)其實(shí)是計(jì)算器,就是算數(shù)用的,在歐洲工業(yè)工業(yè)革命的時(shí)候,大量的工業(yè)模具需要計(jì)算,歐洲又沒有中國(guó)傳統(tǒng)的計(jì)算器 - 算盤,就催生了很多科學(xué)家發(fā)明自己的計(jì)算器(對(duì),就是計(jì)算器,就是以前菜市場(chǎng)還在使用的那種,還不能稱之為現(xiàn)在的計(jì)算機(jī)),這其中有個(gè)NB的人物,這個(gè)人叫布萊士帕斯卡,我們的壓強(qiáng)單位(帕,千帕,百帕,兆帕)等等,就是以這哥們兒的名字命名,還有,計(jì)算機(jī)語(yǔ)言里面有一種叫做Pascal,就是為了紀(jì)念他。

就是這么個(gè)NB的人物,發(fā)明了最早的機(jī)械計(jì)算器,長(zhǎng)這樣兒:

經(jīng)過后人的逐步改進(jìn),機(jī)械計(jì)算機(jī)的最后發(fā)展堪稱精美,有長(zhǎng)這樣兒的:

還有長(zhǎng)成這樣兒的:

還有更NB的:

機(jī)械計(jì)算機(jī)改進(jìn)者中有個(gè)人值得一提,他就是德國(guó)百科全書式的天才,17世紀(jì)的亞里士多德 -- 萊布尼茨!

萊伯尼茲這個(gè)人又是個(gè)大牛,他既懂物理又懂?dāng)?shù)學(xué)(物理數(shù)學(xué)不分家),著名的微積分基本定理,牛頓萊布尼茨公式,就是萊布尼茨發(fā)明的,當(dāng)然這里面是牛頓跟萊布尼茨或者說(shuō)英國(guó)跟歐洲大陸的恩怨情仇。簡(jiǎn)單說(shuō),萊布尼茨發(fā)表論文創(chuàng)立微積分公式,牛頓當(dāng)時(shí)是英國(guó)皇家學(xué)會(huì)的老大,話語(yǔ)權(quán)影響力比較大。牛頓說(shuō)萊布尼茨發(fā)表的公式是參考了牛頓三年前的筆記,萊布尼茨嗓門不夠響,爭(zhēng)不過牛頓,所以沒有辦法,后人就把這個(gè)公式稱為牛頓萊布尼茨公式。

一個(gè)人想在社會(huì)取得回報(bào)或者想發(fā)揮巨大作用,就必須要明白這個(gè)社會(huì)的運(yùn)行機(jī)制,通過這件事兒,大家應(yīng)該明白話語(yǔ)權(quán)的(傳媒、筆桿子)重要性,如果還不能理解,參考美國(guó)把上個(gè)世紀(jì)的美國(guó)病毒命名為西班牙病毒這件事兒,當(dāng)然最近又想把新冠病毒扣在我們腦袋上,就是因?yàn)樗芽亓嗽捳Z(yǔ)權(quán)。衍生出來(lái)你應(yīng)該明白的是,歷史是個(gè)任人打扮的小姑娘,你看到的,你聽到的,都是別人想讓你看到和聽到的,所以你要進(jìn)行深度的思考,他是誰(shuí)?為什么這么說(shuō)?他說(shuō)的是真的嗎?對(duì)我有沒有什么企圖?多問自己幾個(gè)為什么,你會(huì)慢慢從白癡成為智者。

扯遠(yuǎn)了,還說(shuō)回來(lái)萊布尼茨,他除了改進(jìn)機(jī)械計(jì)算機(jī)以外,還有一個(gè)重要的發(fā)明,那就是大名鼎鼎的二進(jìn)制?。ㄟ@里終于跟現(xiàn)代IT技術(shù)關(guān)聯(lián)起來(lái)了)據(jù)說(shuō)二進(jìn)制的發(fā)明是參考中國(guó)古代的陰陽(yáng)太極圖而創(chuàng)作出來(lái)的,對(duì)此,我覺得倒是真的有可能。因?yàn)槿R布尼茨有一本著名的著作,叫做《論中國(guó)人的自然哲學(xué)》,說(shuō)明這個(gè)人對(duì)中國(guó)是有研究的。而且,他發(fā)明了二進(jìn)制以后,還通知了當(dāng)時(shí)的康熙大帝,因?yàn)樗J(rèn)為康熙大帝是個(gè)數(shù)學(xué)迷(對(duì)此我深表懷疑)。

當(dāng)然,機(jī)械計(jì)算機(jī)又大又笨重,早就被現(xiàn)代的電子計(jì)算機(jī)所取代,不過說(shuō)句題外話,機(jī)械計(jì)算機(jī)也有電子計(jì)算機(jī)所不具備的優(yōu)點(diǎn),就是結(jié)實(shí)耐用,幾百年都不壞,而且,還不用電 ,誰(shuí)要是大學(xué)食堂里面打飯收費(fèi)做計(jì)算的時(shí)候來(lái)這么一臺(tái),那絕對(duì)是學(xué)妹眼中最酷的仔!

順便也來(lái)一張現(xiàn)代電子計(jì)算機(jī)的鼻祖(當(dāng)然,第一臺(tái)電子計(jì)算機(jī)這件事兒也是見仁見智,美國(guó)嗓門大,所以現(xiàn)在資料大多認(rèn)為1946誕生于美國(guó)賓夕法尼亞大學(xué)的“ENIAC”是世界上第一臺(tái)電子計(jì)算機(jī)),它長(zhǎng)這樣兒:

這是個(gè)龐然大物,它大概占地一個(gè)別墅,跟一輛前蘇聯(lián)虎式坦克一樣重,每個(gè)小時(shí)耗電150度,但是,每秒鐘的計(jì)算量?jī)H區(qū)區(qū)的5000次,要知道現(xiàn)在手機(jī)上的芯片的計(jì)算速度可以達(dá)到每秒10 0000 0000 0000次。不過就是這樣一臺(tái)還比上菜市場(chǎng)計(jì)算器的東西,開啟了20世紀(jì)最NB的數(shù)字化革命!從此之后,計(jì)算機(jī)行業(yè)飛速發(fā)展,造就了現(xiàn)在所謂的信息化大革命。

嚴(yán)格講,這臺(tái)機(jī)器應(yīng)該稱作電子管計(jì)算機(jī),因?yàn)?,這里面用的零件全部都是電子管,電子管如果開關(guān)的速度太快,很容易就會(huì)壞掉,據(jù)說(shuō)這臺(tái)機(jī)器每天都會(huì)有電子管冒煙兒,工程師在尋找和修復(fù)每一個(gè)電子管中疲于奔命,想象一天24小時(shí),計(jì)算時(shí)間僅有半小時(shí),剩下的23個(gè)半小時(shí)都是在尋找和修復(fù)壞掉的點(diǎn),這是多么讓人抓狂的一件事。如果你不能理解這件事兒,想象一下一個(gè)燈泡每秒不停地開關(guān)5000次,它會(huì)不會(huì)壞掉。而且,電子管還有很嚴(yán)重的發(fā)熱問題,需要把風(fēng)扇進(jìn)行緊密的排布,這也是一個(gè)工藝難題。

不過,幸運(yùn)的是,在這臺(tái)又笨重毛病又多的計(jì)算機(jī)問世的第二年,也就是1947年,美國(guó)貝爾實(shí)驗(yàn)室研究發(fā)明了晶體管,和電子管相比,晶體管體積又小,耗電還低,最重要每秒開關(guān)幾十萬(wàn)上億次都不帶壞的,從這一刻開始,計(jì)算機(jī)革命才真正的進(jìn)入了突飛猛進(jìn)的時(shí)代。

這堂課,我們要講的就是計(jì)算機(jī)的原理。

CPU的原理

為什么講線程要講CPU?因?yàn)榫€程和CPU有一對(duì)一的對(duì)應(yīng)關(guān)系?。ǔ€程除外)

當(dāng)然,現(xiàn)代的計(jì)算機(jī)的核心,也就是芯片,是由10 0000 0000 零件構(gòu)成,我沒有辦法帶你走遍這里面的每一個(gè)細(xì)節(jié),不過,作為高級(jí)語(yǔ)言的程序員,我會(huì)帶你走到足夠深的深度,讓你能夠深入理解你寫的每一行代碼到底在計(jì)算機(jī)內(nèi)部是怎么轉(zhuǎn)換成電信號(hào),從而被精密執(zhí)行的。這一點(diǎn)很重要,因?yàn)檫@會(huì)給你帶來(lái)“通透感”(原諒我找不到更好的形容詞,現(xiàn)在很多程序員是沒有經(jīng)過科班訓(xùn)練的,是根據(jù)業(yè)務(wù)進(jìn)行速成的,對(duì)這樣的小伙伴兒來(lái)說(shuō),你寫的代碼雖然可以工作,但是它對(duì)你是一個(gè)黑盒子,你看不到代碼背后的一切,從而也就無(wú)法進(jìn)行更深入的理解和更準(zhǔn)確的調(diào)優(yōu),總之,我個(gè)人非常喜歡這種通透感,我不喜歡一個(gè)技術(shù)對(duì)我來(lái)說(shuō)是黑盒,是秘密,希望你也能理解和享受這種通透感)

好吧,讓我們揭開代碼背后的神秘世界吧。

還要從一個(gè)故事談起。

我小時(shí)候最喜歡的女同學(xué)叫小芳,長(zhǎng)得好看又善良,我們倆情投意合,每天放學(xué)后都約會(huì)共同進(jìn)步,童年的時(shí)候山青水白,鳥語(yǔ)花香,環(huán)境特別好,我們的年紀(jì)都很小,我愛談天她愛笑,有一回并肩坐在桃樹下,風(fēng)在林梢鳥在叫,不知怎么就睡著了,夢(mèng)里花落知多少...

不要打斷我,讓我陷在美好的回憶中不可自拔一會(huì)兒。

只不過后來(lái)大人發(fā)現(xiàn)了我們的聯(lián)系,用他們自帶的污穢的思想,認(rèn)為我們的關(guān)系是污穢的,是不純潔的,我們當(dāng)時(shí)還沒有羅密歐與朱麗葉,梁山伯與祝英臺(tái)這樣的覺悟,不懂得以死相爭(zhēng),所以就被雙方家長(zhǎng)棒打鴛鴦,各自關(guān)了禁閉。

不過這個(gè)難不倒剛剛學(xué)了電學(xué)的我,我們就設(shè)立了這樣入門級(jí)別的電路:

我還發(fā)明了燈泡語(yǔ)言:

  • 亮亮 = 放
  • 亮滅 = 學(xué)
  • 滅亮 = 等
  • 滅滅 = 我

當(dāng)然你會(huì)發(fā)現(xiàn)如果只有兩個(gè)信號(hào)的組合,就最多表示四個(gè)字,如果想溝通更順暢,我只要增加信號(hào)的組合長(zhǎng)度就可以了,比如三個(gè)信號(hào),我就可以表示八個(gè)字

  • 亮亮亮 = 放
  • 亮亮滅 = 學(xué)
  • 亮滅亮 = 等
  • 亮滅滅 = 我
  • 滅滅亮 = 一
  • 滅滅滅 = 起
  • 滅亮滅 = 電
  • 滅亮亮 = 影

如果想交流的更加復(fù)雜,我可以增加更長(zhǎng)的信號(hào)組合,比如我如果用16個(gè)長(zhǎng)度的信號(hào),就可以表示2^16個(gè)漢字,這個(gè)數(shù)字是65536,要知道,我們?nèi)粘5臐h字常用的話也就4000個(gè)左右,整個(gè)康熙字典的總字?jǐn)?shù)也僅僅47000個(gè),我用燈泡信號(hào)的長(zhǎng)度僅需要16個(gè)信號(hào)長(zhǎng),就足矣涵蓋中文的交流了。

思考題:如果僅需要覆蓋日常交流(4000個(gè)漢字),我需要的信號(hào)組合的長(zhǎng)度至少是多少?

燈泡語(yǔ)言有些復(fù)雜,我結(jié)合萊布尼茨的二進(jìn)制,用1來(lái)代表燈泡亮(通電),用0來(lái)代表燈泡滅(斷電),這樣我和小芳就有了自己的通信語(yǔ)言,比如下面這句話,你猜我說(shuō)了什么?

111 110 001 000 = (? )把答案寫到括號(hào)里。

話說(shuō)到這里,不知道大家有沒有發(fā)現(xiàn),我發(fā)明了一種漢字編碼,就是把特定的漢字用0和1的組合表示出來(lái),注意,漢字的編碼并不是只有一種方式,完全有可能發(fā)生的是,在一種的編碼方式中,111代表'我',而在另外一種編碼方式中111代表'中',如果我們?cè)诮馕鲆欢尉幋a的用錯(cuò)了編碼格式,就會(huì)出現(xiàn)平時(shí)經(jīng)常遇見的'亂碼'問題。

思考題:A編碼中,111 = 我 110 = 你,B編碼中 111 = 沙 110 = 雕,那么下面這段話究竟代表什么呢?
110 111 110

再有了第一個(gè)電路的基礎(chǔ)之上,我有設(shè)計(jì)了下面的電路:

這里就有了輸入和輸出的概念了

輸入1

輸入2

輸出

0

0

0

0

1

0

1

0

0

1

1

1

可以用這樣的符號(hào)表示:

也可以有這樣的電路:

加法器

輸入1

輸入2

加和輸出

進(jìn)位輸出

0

0

0

0

0

1

1

0

1

0

1

0

1

1

0

1

時(shí)鐘

ram 保存信號(hào)

程序 - 自動(dòng)化(時(shí)鐘信號(hào) +

薦書

《編碼 隱匿在計(jì)算機(jī)軟硬件背后的語(yǔ)言》《Code: The Hidden Language of Computer Hardware and Software》

程序的執(zhí)行

進(jìn)程與線程

一個(gè)程序,讀入內(nèi)存,全是0和1構(gòu)成

從內(nèi)存讀入到CPU計(jì)算,這個(gè)時(shí)候要通過總線

怎么區(qū)分一段01的數(shù)據(jù)到底是數(shù)據(jù)還是指令?

總線分類為三種:控制線 地址線 數(shù)據(jù)線

一個(gè)程序的執(zhí)行,首先把可執(zhí)行文件放到內(nèi)存,找到起始(main)的地址,逐步讀出指令和數(shù)據(jù),進(jìn)行計(jì)算并寫回到內(nèi)存。

什么是進(jìn)程?什么是線程?

一個(gè)程序進(jìn)入內(nèi)存,被稱之為進(jìn)程?一個(gè)QQ.exe可以運(yùn)行多份兒?jiǎn)幔?/span>

同一個(gè)進(jìn)程內(nèi)部:有多個(gè)任務(wù)并發(fā)執(zhí)行的需求(比如,一邊計(jì)算,一邊接收網(wǎng)絡(luò)數(shù)據(jù),一邊刷新界面)

能不能用多進(jìn)程?可以,但是毛病多,最嚴(yán)重的毛病是,我可以很輕易的搞死別的進(jìn)程

線程的概念橫空出世:共享空間,不共享計(jì)算

進(jìn)程是靜態(tài)的概念:程序進(jìn)入內(nèi)存,分配對(duì)應(yīng)資源:內(nèi)存空間,進(jìn)程進(jìn)入內(nèi)存,同時(shí)產(chǎn)生一個(gè)主線程

線程是動(dòng)態(tài)的概念:是可執(zhí)行的計(jì)算單元(任務(wù))

一個(gè)ALU同一個(gè)時(shí)間只能執(zhí)行一個(gè)線程

同一段代碼為什么可以被多個(gè)線程執(zhí)行?

線程的切換

保存上下文,保存現(xiàn)場(chǎng)

問題:是不是線程數(shù)量越多,執(zhí)行效率越高?(初級(jí))
展開:調(diào)度算法怎么選?(難)

問題:?jiǎn)魏薈PU多線程執(zhí)行有沒有意義?(初級(jí))

問題:對(duì)于一個(gè)程序,設(shè)置多少個(gè)線程合適?(線程池設(shè)定多少核心線程?)(中高級(jí))

線程調(diào)度器算法(平均時(shí)間片、CFS(考慮權(quán)重))

CPU的并發(fā)控制

關(guān)中斷

緩存一致性協(xié)議

CPU的速度和內(nèi)存的速度(100 :1)

這里的速度值得是ALU訪問寄存器的速度比訪問內(nèi)存的速度快100倍

為了充分利用CPU的計(jì)算能力,在CPU和內(nèi)存中間引入緩存的概念(工業(yè)上的妥協(xié),考慮性價(jià)比)

現(xiàn)在的工業(yè)實(shí)踐,多采用三級(jí)緩存的架構(gòu)

緩存行:一次性讀取的數(shù)據(jù)塊

程序的局部性原理:空間局部性 時(shí)間局部性

如果緩存行大:命中率高,但讀取效率低。如果緩存行?。好新实停x取效率高。

工業(yè)實(shí)踐的妥協(xié)結(jié)果,目前(2021)的計(jì)算機(jī)多采用64bytes (64 * 8bit)為一行

由于緩存行的存在,我們必須有一種機(jī)制,來(lái)保證緩存數(shù)據(jù)的一致性,這種機(jī)制被稱為緩存一致性協(xié)議。

系統(tǒng)屏障

程序真的是按照“順序”執(zhí)行的嗎?

CPU的亂序執(zhí)行

Disorder這個(gè)程序,證明亂序執(zhí)行的確存在

為什么會(huì)亂序?主要是為了提高效率(在等待費(fèi)時(shí)的指令執(zhí)行的時(shí)候,優(yōu)先執(zhí)行后面的指令)

線程的as-if-serial

單個(gè)線程,兩條語(yǔ)句,未必是按順序執(zhí)行

單線程的重排序,必須保證最終一致性

as-if-serial:看上去像是序列化(單線程)

會(huì)產(chǎn)生的后果

多線程會(huì)產(chǎn)生不希望看到的結(jié)果

ThisEscape(this 溢出問題)

推薦《Effective Java》- 不要在構(gòu)造方法中啟動(dòng)線程!

哪些指令可以互換順序

hanppens-before原則(JVM規(guī)定重排序必須遵守的規(guī)則)

JLS17.4.5 (不需要記?。?/span>

·程序次序規(guī)則:同一個(gè)線程內(nèi),按照代碼出現(xiàn)的順序,前面的代碼先行于后面的代碼,準(zhǔn)確的說(shuō)是控制流順序,因?yàn)橐紤]到分支和循環(huán)結(jié)構(gòu)。

·管程鎖定規(guī)則:一個(gè)unlock操作先行發(fā)生于后面(時(shí)間上)對(duì)同一個(gè)鎖的lock操作。

·volatile變量規(guī)則:對(duì)一個(gè)volatile變量的寫操作先行發(fā)生于后面(時(shí)間上)對(duì)這個(gè)變量的讀操作。

·線程啟動(dòng)規(guī)則:Thread的start( )方法先行發(fā)生于這個(gè)線程的每一個(gè)操作。

·線程終止規(guī)則:線程的所有操作都先行于此線程的終止檢測(cè)??梢酝ㄟ^Thread.join( )方法結(jié)束、Thread.isAlive( )的返回值等手段檢測(cè)線程的終止。

·線程中斷規(guī)則:對(duì)線程interrupt( )方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生,可以通過Thread.interrupt( )方法檢測(cè)線程是否中斷

·對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行于發(fā)生它的finalize()方法的開始。

·傳遞性:如果操作A先行于操作B,操作B先行于操作C,那么操作A先行于操作C

禁止編譯器亂序

使用內(nèi)存屏障阻止指令亂序執(zhí)行

內(nèi)存屏障是特殊指令:看到這種指令,前面的必須執(zhí)行完,后面的才能執(zhí)行

intel : lfence sfence mfence(CPU特有指令)

JVM中的內(nèi)存屏障

所有實(shí)現(xiàn)JVM規(guī)范的虛擬機(jī),必須實(shí)現(xiàn)四個(gè)屏障

LoadLoadBarrier LoadStore SL SS

volatile的底層實(shí)現(xiàn)

volatile修飾的內(nèi)存,不可以重排序,對(duì)volatile修飾變量的讀寫訪問,都不可以換順序

1: volatile i

2: ACC_VOLATILE

3: JVM的內(nèi)存屏障

屏障兩邊的指令不可以重排!保障有序!

happends-before

as - if - serial
4:hotspot實(shí)現(xiàn)

bytecodeinterpreter.cpp

int field_offset = cache->f2_as_index(); if (cache->is_volatile()) { if (support_IRIW_for_not_multiple_copy_atomic_cpu) { OrderAccess::fence(); }

orderaccess_linux_x86.inline.hpp

inline void OrderAccess::fence() {   if (os::is_MP()) {     // always use locked addl since mfence is sometimes expensive #ifdef AMD64     __asm__ volatile ('lock; addl $0,0(%%rsp)' : : : 'cc', 'memory'); #else     __asm__ volatile ('lock; addl $0,0(%%esp)' : : : 'cc', 'memory'); #endif   } }

LOCK 用于在多處理器中執(zhí)行指令時(shí)對(duì)共享內(nèi)存的獨(dú)占使用。
它的作用是能夠?qū)?dāng)前處理器對(duì)應(yīng)緩存的內(nèi)容刷新到內(nèi)存,并使其他處理器對(duì)應(yīng)的緩存失效。

另外還提供了有序的指令無(wú)法越過這個(gè)內(nèi)存屏障的作用。

面試題

DCL單例要不要加volatile?(難)

總線/緩存鎖

lock;

薦書 黃俊老師的書《深入理解JAVA并發(fā)原理》(出版中)

操作系統(tǒng)的并發(fā)控制

信號(hào)量與P-V原語(yǔ)

互斥量

自旋鎖

讀寫鎖

中斷控制與內(nèi)核搶占

順序鎖

rcu鎖

JAVA啟動(dòng)線程的5種方法

  1. new MyThread().start()
  2. new Thread(r).start()
  3. new Thread(lamda).start()
  4. ThreadPool
  5. Future Callable and FutureTask

常見的線程方法

我們來(lái)認(rèn)識(shí)幾個(gè)線程的方法

sleep() yield() join()

package com.mashibing.juc.c_000;public class T03_Sleep_Yield_Join { public static void main(String[] args) { //testSleep(); //testYield(); testJoin(); } /*Sleep,意思就是睡眠,當(dāng)前線程暫停一段時(shí)間讓給別的線程去運(yùn)行。Sleep是怎么復(fù)活的?由你的睡眠時(shí)間而定,等睡眠到規(guī)定的時(shí)間自動(dòng)復(fù)活*/ static void testSleep() { new Thread(()->{ for(int i=0; i<100; i++) { System.out.println('A' + i); try { Thread.sleep(500); //TimeUnit.Milliseconds.sleep(500) } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } /*Yield,就是當(dāng)前線程正在執(zhí)行的時(shí)候停止下來(lái)進(jìn)入等待隊(duì)列(就緒狀態(tài),CPU依然有可能把這個(gè)線程拿出來(lái)運(yùn)行),回到等待隊(duì)列里在系統(tǒng)的調(diào)度算法里頭呢還是依然有可能把你剛回去的這個(gè)線程拿回來(lái)繼續(xù)執(zhí)行,當(dāng)然,更大的可能性是把原來(lái)等待的那些拿出一個(gè)來(lái)執(zhí)行,所以yield的意思是我讓出一下CPU,后面你們能不能搶到那我不管*/ static void testYield() { new Thread(()->{ for(int i=0; i<100; i++) { System.out.println('A' + i); if(i%10 == 0) Thread.yield(); } }).start(); new Thread(()->{ for(int i=0; i<100; i++) { System.out.println('------------B' + i); if(i%10 == 0) Thread.yield(); } }).start(); } /*join, 意思就是在自己當(dāng)前線程加入你調(diào)用Join的線程(),本線程等待。等調(diào)用的線程運(yùn)行完了,自己再去執(zhí)行。t1和t2兩個(gè)線程,在t1的某個(gè)點(diǎn)上調(diào)用了t2.join,它會(huì)跑到t2去運(yùn)行,t1等待t2運(yùn)行完畢繼續(xù)t1運(yùn)行(自己join自己沒有意義) */ static void testJoin() { Thread t1 = new Thread(()->{ for(int i=0; i<100; i++) { System.out.println('A' + i); try { Thread.sleep(500); //TimeUnit.Milliseconds.sleep(500) } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread t2 = new Thread(()->{ try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0; i<100; i++) { System.out.println('A' + i); try { Thread.sleep(500); //TimeUnit.Milliseconds.sleep(500) } catch (InterruptedException e) { e.printStackTrace(); } } }); t1.start(); t2.start(); }}

線程狀態(tài)

小節(jié)說(shuō)明:

  • 本節(jié)重要程度:中 (幫助理解線程問題,保障知識(shí)完整性,面試很少考)
  • 本節(jié)難度:低

JAVA的6中線程狀態(tài):

  1. NEW : 線程剛剛創(chuàng)建,還沒有啟動(dòng)
  2. RUNNABLE : 可運(yùn)行狀態(tài),由線程調(diào)度器可以安排執(zhí)行
  3. 包括READY和RUNNING兩種細(xì)分狀態(tài)
  4. WAITING: 等待被喚醒
  5. TIMED WAITING: 隔一段時(shí)間后自動(dòng)喚醒
  6. BLOCKED: 被阻塞,正在等待鎖
  7. TERMINATED: 線程結(jié)束

如下圖:

線程狀態(tài)測(cè)試代碼:

package com.mashibing.juc.c_000_threadbasic;import com.mashibing.util.SleepHelper;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.LockSupport;/** * title:${file_name} * 關(guān)于線程狀態(tài)的實(shí)驗(yàn) * @author 馬士兵 http://www.mashibing.com * @date ${date} * @version 2.0 */public class T04_ThreadState {    static class MyThread extends Thread {        @Override        public void run() {            System.out.println('2: ' + this.getState());            for (int i = 0; i < 10; i++) {                try {                    Thread.sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.print(i + ' ');            }            System.out.println();        }    }    public static void main(String[] args) throws Exception {        Thread t1 = new MyThread();        System.out.println('1: ' + t1.getState());        t1.start();        t1.join();        System.out.println('3: ' + t1.getState());        Thread t2 = new Thread(() -> {            try {                LockSupport.park();                System.out.println('t2 go on!');                TimeUnit.SECONDS.sleep(5);            } catch (InterruptedException e) {                e.printStackTrace();            }        });        t2.start();        TimeUnit.SECONDS.sleep(1);        System.out.println('4: ' + t2.getState());        LockSupport.unpark(t2);        TimeUnit.SECONDS.sleep(1);        System.out.println('5: ' + t2.getState());        final Object o = new Object();        Thread t3 = new Thread(()->{            synchronized (o) {                System.out.println('t3 得到了鎖 o');            }        });        new Thread(()-> {            synchronized (o) {                SleepHelper.sleepSeconds(5);            }        }).start();        SleepHelper.sleepSeconds(1);        t3.start();        SleepHelper.sleepSeconds(1);        System.out.println('6: ' + t3.getState());    }}

線程的打斷

小節(jié)說(shuō)明:

重要程度:中(面試不多)

小節(jié)難度:低

interrupt相關(guān)的三個(gè)方法:

//Thread.java public void interrupt() //t.interrupt() 打斷t線程(設(shè)置t線程某給標(biāo)志位f=true,并不是打斷線程的運(yùn)行)public boolean isInterrupted() //t.isInterrupted() 查詢打斷標(biāo)志位是否被設(shè)置(是不是曾經(jīng)被打斷過)public static boolean interrupted()//Thread.interrupted() 查看“當(dāng)前”線程是否被打斷,如果被打斷,恢復(fù)標(biāo)志位
  1. interrupt() :實(shí)例方法,設(shè)置線程中斷標(biāo)志(打擾一下,你該處理一下中斷)
  2. isInterrupted():實(shí)例方法,有沒有人打擾我?
  3. interrupted():靜態(tài)方法,有沒有人打擾我(當(dāng)前線程)?復(fù)位!

interrupt和sleep() wait() join()

sleep()方法在睡眠的時(shí)候,不到時(shí)間是沒有辦法叫醒的,這個(gè)時(shí)候可以用interrupt設(shè)置標(biāo)志位,然后呢必須得catch InterruptedException來(lái)進(jìn)行處理,決定繼續(xù)睡或者是別的邏輯,(自動(dòng)進(jìn)行中斷標(biāo)志復(fù)位)

interrupt是否能中斷正在競(jìng)爭(zhēng)鎖的線程

package com.mashibing.juc.c_000_threadbasic;import com.mashibing.util.SleepHelper;/** * interrupt與sleep() wait() join() */public class T09_Interrupt_and_sync {    private static Object o = new Object();    public static void main(String[] args) {        Thread t1 = new Thread(()-> {            synchronized (o) {                SleepHelper.sleepSeconds(10);            }        });        t1.start();        SleepHelper.sleepSeconds(1);        Thread t2 = new Thread(()-> {            synchronized (o) {            }            System.out.println('t2 end!');        });        t2.start();        t2.interrupt();    }}

interrupt()不能打斷正在競(jìng)爭(zhēng)鎖的線程synchronized()

如果想打斷正在競(jìng)爭(zhēng)鎖的線程,使用ReentrantLock的lockInterruptibly()

package com.mashibing.juc.c_000_threadbasic;import com.mashibing.util.SleepHelper;import java.util.concurrent.locks.ReentrantLock;/** * interrupt與lockInterruptibly() */public class T11_Interrupt_and_lockInterruptibly { private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { Thread t1 = new Thread(()-> { lock.lock(); try { SleepHelper.sleepSeconds(10); } finally { lock.unlock(); } System.out.println('t1 end!'); }); t1.start(); SleepHelper.sleepSeconds(1); Thread t2 = new Thread(()-> { System.out.println('t2 start!'); try { lock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } System.out.println('t2 end!'); }); t2.start(); SleepHelper.sleepSeconds(1); t2.interrupt(); }}

優(yōu)雅的結(jié)束線程

小節(jié)說(shuō)明:

本節(jié)內(nèi)容的重要程度:中(面試有可能被問)

小節(jié)難度:低

結(jié)束線程的方法:

  1. 自然結(jié)束(能自然結(jié)束就盡量自然結(jié)束)
  2. stop() suspend() resume()
  3. volatile標(biāo)志
    1. 不適合某些場(chǎng)景(比如還沒有同步的時(shí)候,線程做了阻塞操作,沒有辦法循環(huán)回去)
    2. 打斷時(shí)間也不是特別精確,比如一個(gè)阻塞容器,容量為5的時(shí)候結(jié)束生產(chǎn)者,
      但是,由于volatile同步線程標(biāo)志位的時(shí)間控制不是很精確,有可能生產(chǎn)者還繼續(xù)生產(chǎn)一段兒時(shí)間
  4. interrupt() and isInterrupted(比較優(yōu)雅)

線程組

(不重要,暫忽略。)

ThreadGroups - Thread groups are best viewed as an unsuccessful experiment , and you may simply ignore their existence! - Joshua Bloch one of JDK designers

并發(fā)編程的特性

可見性

有序性

原子性

線程的原子性

從一個(gè)簡(jiǎn)單的小程序談起:

package com.mashibing.juc.c_001_sync_basics;import java.util.concurrent.CountDownLatch;public class T00_IPlusPlus {    private static long n = 0L;    public static void main(String[] args) throws Exception {        Thread[] threads = new Thread[100];        CountDownLatch latch = new CountDownLatch(threads.length);        for (int i = 0; i < threads.length; i++) {            threads[i] = new Thread(() -> {                for (int j = 0; j < 10000; j++) {                    //synchronized (T00_IPlusPlus.class) {                    n++;                    //}                }                latch.countDown();            });        }        for (Thread t : threads) {            t.start();        }        latch.await();        System.out.println(n);    }}

一些基本概念

race condition => 競(jìng)爭(zhēng)條件 , 指的是多個(gè)線程訪問共享數(shù)據(jù)的時(shí)候產(chǎn)生競(jìng)爭(zhēng)

數(shù)據(jù)的不一致(unconsistency),并發(fā)訪問之下產(chǎn)生的不期望出現(xiàn)的結(jié)果

如何保障數(shù)據(jù)一致呢?--> 線程同步(線程執(zhí)行的順序安排好),

monitor (管程) ---> 鎖

critical section -> 臨界區(qū)

如果臨界區(qū)執(zhí)行時(shí)間長(zhǎng),語(yǔ)句多,叫做 鎖的粒度比較粗,反之,就是鎖的粒度比較細(xì)

具體: 保障操作的原子性(Atomicity)

  1. 悲觀鎖:悲觀的認(rèn)為這個(gè)操作會(huì)被別的線程打斷(悲觀鎖)synchronized(上一個(gè)小程序)
  2. 樂觀鎖:樂觀的認(rèn)為這個(gè)做不會(huì)被別的線程打斷(樂觀鎖 自旋鎖 無(wú)鎖)cas操作
    CAS = Compare And Set/Swap/Exchange
    /** * 解決同樣的問題的更高效的方法,使用AtomXXX類 * AtomXXX類本身方法都是原子性的,但不能保證多個(gè)方法連續(xù)調(diào)用是原子性的 * @author mashibing */ package com.mashibing.juc.c_018_00_AtomicXXX; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class T01_AtomicInteger { /*volatile*/ //int count1 = 0; AtomicInteger count = new AtomicInteger(0); /* synchronized */void m() { for (int i = 0; i < 10000; i++) //if count1.get() < 1000 count.incrementAndGet(); //count1++ } public static void main(String[] args) { T01_AtomicInteger t = new T01_AtomicInteger(); List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 100; i++) { threads.add(new Thread(t::m, 'thread-' + i)); } threads.forEach((o) -> o.start()); threads.forEach((o) -> { try { o.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(t.count); } }

我們平時(shí)所說(shuō)的'上鎖',一般指的是悲觀鎖

上鎖的本質(zhì)

上鎖的本質(zhì)是把并發(fā)編程序列化

package com.mashibing.juc.c_001_sync_basics;import com.mashibing.util.SleepHelper;public class T00_01_WhatIsLock { private static Object o = new Object(); public static void main(String[] args) { Runnable r = () -> { //synchronized (o) { //打開注釋試試看,對(duì)比結(jié)果 System.out.println(Thread.currentThread().getName() + ' start!'); SleepHelper.sleepSeconds(2); System.out.println(Thread.currentThread().getName() + ' end!'); //} }; for (int i = 0; i < 3; i++) { new Thread(r).start(); } }}

同時(shí)保障可見性

注意序列化并非其他程序一直沒機(jī)會(huì)執(zhí)行,而是有可能會(huì)被調(diào)度,但是搶不到鎖,又回到Blocked或者Waiting狀態(tài)(sync鎖升級(jí))

一定是鎖定同一把鎖(搶一個(gè)坑位)

package com.mashibing.juc.c_001_sync_basics;import com.mashibing.util.SleepHelper;public class T00_02_SingleLockVSMultiLock {    private static Object o1 = new Object();    private static Object o2 = new Object();    private static Object o3 = new Object();    public static void main(String[] args) {        Runnable r1 = () -> {            synchronized (o1) {                System.out.println(Thread.currentThread().getName() + ' start!');                SleepHelper.sleepSeconds(2);                System.out.println(Thread.currentThread().getName() + ' end!');            }        };        Runnable r2 = () -> {            synchronized (o2) {                System.out.println(Thread.currentThread().getName() + ' start!');                SleepHelper.sleepSeconds(2);                System.out.println(Thread.currentThread().getName() + ' end!');            }        };        Runnable r3 = () -> {            synchronized (o3) {                System.out.println(Thread.currentThread().getName() + ' start!');                SleepHelper.sleepSeconds(2);                System.out.println(Thread.currentThread().getName() + ' end!');            }        };        new Thread(r1).start();        new Thread(r2).start();        new Thread(r3).start();    }}

什么樣的語(yǔ)句(指令)具備原子性?

CPU級(jí)別匯編,需要查詢匯編手冊(cè)!

Java中的8大原子操作:(了解即可,無(wú)需背過)

  1. lock:主內(nèi)存,標(biāo)識(shí)變量為線程獨(dú)占
  2. unlock:主內(nèi)存,解鎖線程獨(dú)占變量
  3. read:主內(nèi)存,讀取內(nèi)存到線程緩存(工作內(nèi)存)
  4. load:工作內(nèi)存,read后的值放入線程本地變量副本
  5. use:工作內(nèi)存,傳值給執(zhí)行引擎
  6. assign:工作內(nèi)存,執(zhí)行引擎結(jié)果賦值給線程本地變量
  7. store:工作內(nèi)存,存值到主內(nèi)存給write備用
  8. write:主內(nèi)存,寫變量值

JVM中的兩中鎖

重量級(jí)鎖(經(jīng)過操作系統(tǒng)的調(diào)度)synchronized早期都是這種鎖(目前的實(shí)現(xiàn)中升級(jí)到最后也是這種鎖)

輕量級(jí)鎖(CAS的實(shí)現(xiàn),不經(jīng)過OS調(diào)度)(無(wú)鎖 - 自旋鎖 - 樂觀鎖)

CAS深度剖析

CAS的ABA問題解決方案 - Version

CAS操作本身的原子性保障

AtomicInteger:

public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }

Unsafe:

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

運(yùn)用:

package com.mashibing.jol;import sun.misc.Unsafe;import java.lang.reflect.Field;public class T02_TestUnsafe { int i = 0; private static T02_TestUnsafe t = new T02_TestUnsafe(); public static void main(String[] args) throws Exception { //Unsafe unsafe = Unsafe.getUnsafe(); Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); Field f = T02_TestUnsafe.class.getDeclaredField('i'); long offset = unsafe.objectFieldOffset(f); System.out.println(offset); boolean success = unsafe.compareAndSwapInt(t, offset, 0, 1); System.out.println(success); System.out.println(t.i); //unsafe.compareAndSwapInt() }}

jdk8u: unsafe.cpp:

cmpxchg = compare and exchange set swap

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))  UnsafeWrapper('Unsafe_CompareAndSwapInt');  oop p = JNIHandles::resolve(obj);  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;UNSAFE_END

jdk8u:
atomic_linux_x86.inline.hpp
93行

is_MP = Multi Processors

inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { int mp = os::is_MP(); __asm__ volatile (LOCK_IF_MP(%4) 'cmpxchgl %1,(%3)' : '=a' (exchange_value) : 'r' (exchange_value), 'a' (compare_value), 'r' (dest), 'r' (mp) : 'cc', 'memory'); return exchange_value;}

jdk8u: os.hpp is_MP()

static inline bool is_MP() {    // During bootstrap if _processor_count is not yet initialized    // we claim to be MP as that is safest. If any platform has a    // stub generator that might be triggered in this phase and for    // which being declared MP when in fact not, is a problem - then    // the bootstrap routine for the stub generator needs to check    // the processor count directly and leave the bootstrap routine    // in place until called after initialization has ocurred.    return (_processor_count != 1) || AssumeMP;  }

jdk8u: atomic_linux_x86.inline.hpp

#define LOCK_IF_MP(mp) 'cmp $0, ' #mp '; je 1f; lock; 1: '

最終實(shí)現(xiàn):

cmpxchg = cas修改變量值

lock cmpxchg 指令

硬件:

lock指令在執(zhí)行的時(shí)候視情況采用緩存鎖或者總線鎖

兩種鎖的效率

不同的場(chǎng)景:

臨界區(qū)執(zhí)行時(shí)間比較長(zhǎng) , 等的人很多 -> 重量級(jí)

時(shí)間短,等的人少 -> 自旋鎖

synchronized如何保障可見性

JVM中的線程和OS線程對(duì)應(yīng)關(guān)系

JVM 1:1 -> LOOM -> M:N (golang)

synchronized鎖升級(jí)過程

(內(nèi)容太多,見另一篇文檔)

常見的鎖類型

  1. 悲觀鎖 樂觀鎖
  2. 自旋鎖(CAS)
  3. 讀寫鎖
  4. 排他鎖 共享鎖
  5. 分段鎖
  6. 死鎖 活鎖
  7. 數(shù)據(jù)庫(kù)的行鎖 表鎖 間隙鎖 ...
  8. 偏向鎖
  9. 可重入鎖

題外話:有沒有程序天生就是線程安全的?

有沒有一門編程語(yǔ)言天生安全,目前有一門RUST,但是由于語(yǔ)言難度較大,同時(shí)缺乏強(qiáng)有力的團(tuán)隊(duì)推廣,目前并不是很流行,對(duì)RUST有了解興趣的,參考馬士兵老師《RUST》

一些大廠難題

線程喚醒問題(阿里)

樣例代碼

public class Test { /** * 有三個(gè)線程 A,B,C * A為什么總是在C前面搶到鎖??? */ private final static Object LOCK = new Object(); public void startThreadA() { new Thread(() -> { synchronized (LOCK) { System.out.println(Thread.currentThread().getName() + ': get lock'); //啟動(dòng)線程b startThreadB(); System.out.println(Thread.currentThread().getName() + ': start wait'); try { //線程a wait LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ': get lock after wait'); System.out.println(Thread.currentThread().getName() + ': release lock'); } }, 'thread-A').start(); } private void startThreadB() { new Thread(() -> { synchronized (LOCK) { System.out.println(Thread.currentThread().getName() + ': get lock'); //啟動(dòng)線程c startThreadC(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ': start notify'); //線程b喚醒其他線程 LOCK.notify(); System.out.println(Thread.currentThread().getName() + ': release lock'); } }, 'thread-B').start(); } private void startThreadC() { new Thread(() -> { System.out.println(Thread.currentThread().getName() + ': thread c start'); synchronized (LOCK) { System.out.println(Thread.currentThread().getName() + ': get lock'); System.out.println(Thread.currentThread().getName() + ': release lock'); } }, 'thread-C').start(); } public static void main(String[] args) { new Test().startThreadA(); } }

輸出結(jié)果:

thread-A: get lock thread-A: start wait thread-B: get lock thread-C: thread c start thread-B: start notify thread-B: release lock thread-A: get lock after wait thread-A: release lock thread-C: get lock thread-C: release lock

問題:

為什么每次運(yùn)行,線程A總是優(yōu)先于線程C獲取鎖

分析:

在Hotspot源碼中,我們知道synchronized關(guān)鍵字是通過monitor_enter和monitor_exit字節(jié)來(lái)實(shí)現(xiàn)的,最終用于阻塞線程的對(duì)象為ObjectMonitor對(duì)象,該對(duì)象包含三個(gè)關(guān)鍵字段:*WaitSet、*cxq、*EntryList。*WaitSet用于保存使用wait方法釋放獲得的synchronized鎖對(duì)象的線程,也即我們調(diào)用wait函數(shù),那么當(dāng)前線程將會(huì)釋放鎖,并將自身放入等待集中。而cxq隊(duì)列用于存放競(jìng)爭(zhēng)ObjectMonitor鎖對(duì)象失敗的線程,而_EntryList用于也用于存放競(jìng)爭(zhēng)鎖失敗的線程。那么它們之間有何區(qū)別呢?這是由于我們需要頻繁的釋放和獲取鎖,當(dāng)我們獲取鎖失敗那么將需要把線程放入競(jìng)爭(zhēng)列表中,當(dāng)喚醒時(shí)需要從競(jìng)爭(zhēng)列表中獲取線程喚醒獲取鎖,而如果我們只用一個(gè)列表來(lái)完成這件事,那么將會(huì)導(dǎo)致鎖爭(zhēng)用導(dǎo)致CPU資源浪費(fèi)且影響性能,這時(shí)我們獨(dú)立出兩個(gè)列表,其中cxq列表用于競(jìng)爭(zhēng)放入線程,而entrylist用于單線程喚醒操作。具體策略是這樣的:

  1. 線程競(jìng)爭(zhēng)鎖失敗后CAS放入cxq列表中
  2. 線程釋放鎖后將根據(jù)策略來(lái)喚醒cxq或者entrylist中的線程(我們這里只討論默認(rèn)策略)
  3. 默認(rèn)策略下優(yōu)先喚醒entrylist列表中的線程,因?yàn)閱拘丫€程對(duì)象的操作是單線程的,也即只有獲取鎖并且釋放鎖的線程可以操作,所以操作entrylist是線程安全的
  4. 如果entrylist列表為空,那么將會(huì)CAS將cxq中的等待線程一次性獲取到entrylist中并開始逐個(gè)喚醒

在hotspot中我們稱這種算法為電梯算法,也即將需要喚醒的線程一次性從競(jìng)爭(zhēng)隊(duì)列中放入entrylist喚醒隊(duì)列。

那么這時(shí)我們就可以分析以上代碼為何總是喚醒線程A了,我們先看線程執(zhí)行順序,首先啟動(dòng)線程A,隨后線程A啟動(dòng)線程B,B線程需要獲取對(duì)象鎖從而創(chuàng)建線程C,我們看到當(dāng)線程A調(diào)用wait方法將自己放入等待集中后,將會(huì)喚醒線程B,隨后線程B創(chuàng)建并啟動(dòng)了線程C,然后等待C開始執(zhí)行,由于此時(shí)對(duì)象鎖由線程B持有,所以線程C需要放入cxq競(jìng)爭(zhēng)隊(duì)列,隨后B從睡眠中醒來(lái),執(zhí)行notify方法,該方法總是喚醒了線程A而不是C,也即優(yōu)先處理等待集中的線程而不是cxq競(jìng)爭(zhēng)隊(duì)列的線程。那么我們通過notify方法來(lái)看看實(shí)現(xiàn)原理。Notify便是Wait操作的反向操作,所以這里很簡(jiǎn)單,無(wú)非就是將線程從等待集中移出并且喚醒。源碼如下。

JVM_ENTRY(void, JVM_MonitorNotify(JNIEnv* env, jobject handle)) Handle obj(THREAD, JNIHandles::resolve_non_null(handle)); // 直接調(diào)用ObjectSynchronizer::notify ObjectSynchronizer::notify(obj, CHECK); JVM_END

這里直接跟進(jìn)ObjectSynchronizer::notify。源碼如下。

void ObjectSynchronizer::notify(Handle obj, TRAPS) {     if (UseBiasedLocking) {          // 如果使用偏向鎖,那么取消偏向鎖         BiasedLocking::revoke_and_rebias(obj, false, THREAD);     }     markOop mark = obj->mark();     if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {          // 如果是輕量級(jí)鎖,那么直接返回,因?yàn)閣ait操作需要通過對(duì)象監(jiān)視器來(lái)做         return;     }     ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD); }

可以看到最終調(diào)用了ObjectSynchronizer的notify方法來(lái)喚醒。源碼如下。

void ObjectMonitor::notify(TRAPS) { CHECK_OWNER(); if (_WaitSet == NULL) { // 如果等待集為空,直接返回 return ; } int Policy = Knob_MoveNotifyee ; // 移動(dòng)策略,這里默認(rèn)是2 Thread::SpinAcquire (&_WaitSetLock, 'WaitSet - notify') ; // 首先對(duì)等待集上自旋鎖 // 調(diào)用DequeueWaiter將一個(gè)等待線程從等待集中拿出來(lái) ObjectWaiter * iterator = DequeueWaiter() ; if (iterator != NULL) { if (Policy != 4) { // 如果策略不等于4那么將線程的狀態(tài)修改為TS_ENTER iterator->TState = ObjectWaiter::TS_ENTER ; } iterator->_notified = 1 ; // 喚醒計(jì)數(shù)器 Thread * Self = THREAD; iterator->_notifier_tid = Self->osthread()->thread_id(); ObjectWaiter * List = _EntryList ; if (Policy == 0) { // 如果策略為0,那么頭插入到entrylist中 if (List == NULL) { // 如果entrylist為空,那么將當(dāng)前監(jiān)視器直接作為_EntryList 頭結(jié)點(diǎn) iterator->_next = iterator->_prev = NULL ; _EntryList = iterator ; } else { // 否則頭插 List->_prev = iterator ; iterator->_next = List ; iterator->_prev = NULL ; _EntryList = iterator ; } } else if (Policy == 1) { // 如果策略為1,那么插入entrylist的尾部 if (List == NULL) { iterator->_next = iterator->_prev = NULL ; _EntryList = iterator ; } else { ObjectWaiter * Tail ; for (Tail = List ; Tail->_next != NULL ; Tail = Tail->_next) ; Tail->_next = iterator ; iterator->_prev = Tail ; iterator->_next = NULL ; } } else if (Policy == 2) { // 如果策略為2,那么如果entrylist為空,那么插入entrylist,否則插入cxq隊(duì)列 if (List == NULL) { iterator->_next = iterator->_prev = NULL ; _EntryList = iterator ; } else { iterator->TState = ObjectWaiter::TS_CXQ ; for (;;) { ObjectWaiter * Front = _cxq ; iterator->_next = Front ; if (Atomic::cmpxchg_ptr (iterator, &_cxq, Front) == Front) { break ; } } } } else if (Policy == 3) { // 如果策略為3,那么直接插入cxq iterator->TState = ObjectWaiter::TS_CXQ ; for (;;) { ObjectWaiter * Tail ; Tail = _cxq ; if (Tail == NULL) { iterator->_next = NULL ; if (Atomic::cmpxchg_ptr (iterator, &_cxq, NULL) == NULL) { break ; } } else { while (Tail->_next != NULL) Tail = Tail->_next ; Tail->_next = iterator ; iterator->_prev = Tail ; iterator->_next = NULL ; break ; } } } else { // 否則直接喚醒線程,讓線程自己去調(diào)用enterI進(jìn)入監(jiān)視器 ParkEvent * ev = iterator->_event ; iterator->TState = ObjectWaiter::TS_RUN ; OrderAccess::fence() ; ev->unpark() ; } } Thread::SpinRelease (&_WaitSetLock) ; // 釋放等待集自旋鎖 }

這里有一個(gè)方法DequeueWaiter() 將線程從等待集中取出來(lái),這里的notify讀者都知喚醒一個(gè),很多人都說(shuō)隨機(jī)喚醒一個(gè),那么我們這里來(lái)看看喚醒算法是什么。源碼如下。

inline ObjectWaiter* ObjectMonitor::DequeueWaiter() {     ObjectWaiter* waiter = _WaitSet;        // 很簡(jiǎn)單對(duì)吧,直接從頭部拿     if (waiter) {                       // 如果waiter不為空,那么從等待集中斷鏈         DequeueSpecificWaiter(waiter);     }     return waiter; } inline void ObjectMonitor::DequeueSpecificWaiter(ObjectWaiter* node) {     ObjectWaiter* next = node->_next;     if (next == node) {                 // 如果只有一個(gè)節(jié)點(diǎn),那么直接將等待集清空即可         _WaitSet = NULL;     } else {                            // 否則雙向鏈表的斷鏈基礎(chǔ)操作         ObjectWaiter* prev = node->_prev;         next->_prev = prev;         prev->_next = next;         if (_WaitSet == node) {             _WaitSet = next;         }     }     // 斷開連接后,也需要把斷下來(lái)的節(jié)點(diǎn),next和prev指針清空     node->_next = NULL;     node->_prev = NULL; }

那么讀者應(yīng)該可以明顯的看到,底層對(duì)于喚醒操作是從等待集的頭部選擇線程喚醒。

總結(jié)

通過源碼我們看到,為何總是喚醒線程A,這是用于當(dāng)線程C競(jìng)爭(zhēng)不到鎖時(shí),被放入了cxq隊(duì)列,而此時(shí)entrylist為null,線程A在等待集waitset中,當(dāng)我們調(diào)用notify方法時(shí),由于移動(dòng)策略默認(rèn)是2,這時(shí)會(huì)從等待集的頭部將線程A取下,放入到entrylist中,當(dāng)notify執(zhí)行完畢后,在執(zhí)行后面的monitor_exit字節(jié)碼時(shí)將會(huì)優(yōu)先從entrylist中喚醒線程,這就導(dǎo)致了A線程總是被優(yōu)先執(zhí)行。

線程執(zhí)行完isAlive方法返回true問題(谷歌)

樣例代碼:

public class ThreadAliveTest { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { System.out.println('t1 start'); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println('t1 end'); }); t1.start(); Thread t2 = new Thread(() -> { synchronized (t1) { System.out.println('t2 start'); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println('t1 isAlive:' + t1.isAlive()); } }); t2.start(); } }

輸出結(jié)果:t1 start t2 start t1 end t1 isAlive:true

問題:

為什么線程結(jié)束了,isAlive方法還返回true

分析:

我們首先看看執(zhí)行流程,線程T1啟動(dòng)后將會(huì)睡眠2秒,隨后2秒后執(zhí)行結(jié)束,隨后線程T2啟動(dòng),T2首先獲取到T1的對(duì)象鎖,然后睡眠5秒,隨后調(diào)用T1的isAlive方法判定線程是否存活,那么為什么會(huì)輸出true呢?我們還得先看看isAlive方法如何實(shí)現(xiàn)的。我們來(lái)看源碼。

public final native boolean isAlive();

首先看到isAlive方法由JNI方法實(shí)現(xiàn)。我們來(lái)看Hotspot源碼。

JVM_ENTRY(jboolean, JVM_IsThreadAlive(JNIEnv* env, jobject jthread)) JVMWrapper('JVM_IsThreadAlive'); oop thread_oop = JNIHandles::resolve_non_null(jthread); return java_lang_Thread::is_alive(thread_oop); JVM_END

我們看到首先通過resolve_non_null方法將jthread轉(zhuǎn)為oop對(duì)象thread_oop,隨后調(diào)用java_lang_Thread的is_alive方法來(lái)判斷是否存活,我們繼續(xù)跟進(jìn)。

bool java_lang_Thread::is_alive(oop java_thread) {   JavaThread* thr = java_lang_Thread::thread(java_thread);   return (thr != NULL); } JavaThread* java_lang_Thread::thread(oop java_thread) {   return (JavaThread*)java_thread->address_field(_eetop_offset); }

我們看到最后是通過獲取java thread對(duì)象,也即java的Thread類中的eetop屬性,如果該屬性為null,那么表明線程已經(jīng)銷毀,也即返回false,如果eetop還在那么返回true,表明線程存活。那么什么是eetop呢?我們還得從線程創(chuàng)建方法入手。

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) JVMWrapper('JVM_StartThread'); JavaThread *native_thread = NULL; bool throw_illegal_thread_state = false; // 非法線程狀態(tài)標(biāo)識(shí) { // Threads_lock上鎖,保證C++的線程對(duì)象和操作系統(tǒng)原生線程不會(huì)被清除。當(dāng)前方法執(zhí)行完,也就是棧幀釋放時(shí),會(huì)釋放這里的鎖,當(dāng)然肯定會(huì)調(diào)用析構(gòu)函數(shù),而這個(gè)對(duì)象的析構(gòu)函數(shù)中調(diào)用unlock方法釋放鎖 MutexLocker mu(Threads_lock); if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) { // 如果線程不為空,則表明線程已經(jīng)啟動(dòng),則為非法狀態(tài) throw_illegal_thread_state = true; } else { // 本來(lái)這里可以檢測(cè)一下stillborn標(biāo)記來(lái)看看線程是否已經(jīng)停止,但是由于歷史原因,就讓線程自己玩了,這里就不玩了 // 取得線程對(duì)象的stackSize的大小 jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread)); // 開始創(chuàng)建C++ Thread對(duì)象和原生線程對(duì)象,使用無(wú)符號(hào)的線程棧大小,所以這里不會(huì)出現(xiàn)負(fù)數(shù) size_t sz = size > 0 ? (size_t) size : 0; // 創(chuàng)建JavaThread,這里的thread_entry為傳入的運(yùn)行地址,也就是啟動(dòng)線程,需要一個(gè)入口執(zhí)行點(diǎn),這個(gè)函數(shù)地址便是入口執(zhí)行點(diǎn) native_thread = new JavaThread(&thread_entry, sz); // 如果osthread不為空,則標(biāo)記當(dāng)前線程還沒有被使用 if (native_thread->osthread() != NULL) { native_thread->prepare(jthread); } } } // 如果throw_illegal_thread_state不為0,那么直接拋出異常 if (throw_illegal_thread_state) { THROW(vmSymbols::java_lang_IllegalThreadStateException()); } // 原生線程必然不能為空,因?yàn)榫€程是由操作系統(tǒng)創(chuàng)建的,所以沒有OS線程,空有個(gè)JavaThread類有啥用0.0 if (native_thread->osthread() == NULL) { delete native_thread; // 直接用C++的delete釋放內(nèi)存 THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),'unable to create new native thread'); } Thread::start(native_thread); // 一切準(zhǔn)備妥當(dāng),開始啟動(dòng)線程 JVM_END

我們看到首先創(chuàng)建了JavaThread對(duì)象,該對(duì)象內(nèi)部創(chuàng)建了OSThread對(duì)象,我們這么理解:JavaThread代表了C++層面的Java線程,而OSThread代表了操作系統(tǒng)層面的線程對(duì)象。隨后調(diào)用了native_thread->prepare(jthread)方法為啟動(dòng)線程做準(zhǔn)備。我們關(guān)注該方法。

void JavaThread::prepare(jobject jni_thread, ThreadPriority prio) {     // 包裝當(dāng)前Java線程對(duì)象     Handle thread_oop(Thread::current(),                       JNIHandles::resolve_non_null(jni_thread));     // 將Java層面的線程Oop對(duì)象與JavaThread C++層面的對(duì)象關(guān)聯(lián)     set_threadObj(thread_oop());     java_lang_Thread::set_thread(thread_oop(), this);     // 設(shè)置優(yōu)先級(jí)     if (prio == NoPriority) {         prio = java_lang_Thread::priority(thread_oop());     }     Thread::set_priority(this, prio);     // 將JavaThread類放入到全局線程列表中     Threads::add(this); }

我們注意看
java_lang_Thread::set_thread方法。我們跟進(jìn)它的源碼。

void java_lang_Thread::set_thread(oop java_thread, JavaThread* thread) { // 將JavaThread C++層面的線程對(duì)象設(shè)置為Java層面的Thread oop對(duì)象的eetop變量 java_thread->address_field_put(_eetop_offset, (address)thread); }

這下我們知道了eetop變量即使JavaThread對(duì)象的地址信息。在了解完eetop如何被設(shè)置之后我們得繼續(xù)看,eetop什么時(shí)候被取消。當(dāng)Java線程執(zhí)行完Runnable接口的run方法最后一個(gè)字節(jié)碼后,將會(huì)調(diào)用exit方法。該方法完成線程對(duì)象的退出和清理操作,我們重點(diǎn)看ensure_join方法。

void JavaThread::exit(bool destroy_vm, ExitType exit_type) {     ...     ensure_join(this);     ... }

我們繼續(xù)跟進(jìn)ensure_join的源碼實(shí)現(xiàn)。

static void ensure_join(JavaThread* thread) { // 封裝Java Thread線程oop對(duì)象 Handle threadObj(thread, thread->threadObj()); // 獲取Java Thread線程oop對(duì)象鎖 ObjectLocker lock(threadObj, thread); // 清除未處理的異常信息 thread->clear_pending_exception(); // 將狀態(tài)修改為TERMINATED java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED); // 將Java Thread線程oop對(duì)象與JavaThread C++對(duì)象解綁 java_lang_Thread::set_thread(threadObj(), NULL); // 喚醒所有阻塞在線程對(duì)象的線程 lock.notify_all(thread); // 如果以上代碼期間發(fā)生異常,那么清理掛起的異常 thread->clear_pending_exception(); }

我們看到最終由ensure_join方法中的
java_lang_Thread::set_thread(threadObj(), NULL),將eetop變量設(shè)置為null,當(dāng)執(zhí)行完這一步時(shí),我們?cè)偻ㄟ^isAlive方法判斷線程是否存活時(shí),將返回false,否則返回true。而我們看到在操作該變量時(shí)需要獲取線程對(duì)象鎖。我們來(lái)看ObjectLocker的構(gòu)造函數(shù)和析構(gòu)函數(shù)的實(shí)現(xiàn)。

ObjectLocker::ObjectLocker(Handle obj, Thread* thread, bool doLock) {     _dolock = doLock;     _thread = thread;     if (_dolock) {         // 獲取Java Thread線程oop對(duì)象鎖         ObjectSynchronizer::fast_enter(_obj, &_lock, false, _thread);     } }  ObjectLocker::~ObjectLocker() {     if (_dolock) {         // 釋放Java Thread線程oop對(duì)象鎖         ObjectSynchronizer::fast_exit(_obj(), &_lock, _thread);     } }

我們看到當(dāng)我們創(chuàng)建ObjectLocker對(duì)象時(shí),會(huì)在構(gòu)造函數(shù)中獲取到線程對(duì)象鎖,而當(dāng)ensure_join方法執(zhí)行完畢后,將會(huì)調(diào)用ObjectLocker的析構(gòu)函數(shù),在該函數(shù)中釋放線程對(duì)象鎖。

總結(jié):

這下我們就可以通過以上知識(shí)來(lái)分析為何isAlive方法在線程執(zhí)行完畢后仍然返回true了,這是用于isAlive方法通過判斷Java線程對(duì)象的eetop變量來(lái)判定線程是否存活,而當(dāng)我們線程執(zhí)行完畢后將會(huì)調(diào)用exit方法,該方法將會(huì)調(diào)用ensure_join方法,在該方法中將eetop甚至為null,但是由于賦值前需要獲取到Java線程的對(duì)象鎖,而該對(duì)象的對(duì)象鎖已經(jīng)由線程T2持有,這時(shí)當(dāng)前線程將會(huì)阻塞,從而造成eetop變量沒有被清除,從而導(dǎo)致isAlive方法在T1線程執(zhí)行完畢后仍然返回true。讀者也可以看看java Thread的源碼,join函數(shù)也是通過對(duì)Thread對(duì)象獲取鎖然后調(diào)用isAlive來(lái)判定線程是否結(jié)束的,這就意味著如果我們用別的線程持有了Java Thread的對(duì)象鎖,那么這時(shí)調(diào)用join方法的線程也是會(huì)被阻塞的。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
超全的多線程面經(jīng)附答案
并發(fā)編程奪命60連問,你頂?shù)米讉€(gè)?
面經(jīng)手冊(cè) · 第15篇《碼農(nóng)會(huì)鎖,synchronized 解毒,剖析源碼深度分析!》
思維導(dǎo)圖整理Java并發(fā)基礎(chǔ)知識(shí)
Java多線程編程-看了這篇關(guān)于ThreadLocal的原理應(yīng)該透徹了
深入學(xué)習(xí)synchronized
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服