有很多民族自豪感爆棚的兄弟會(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?因?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ǔ)言:
當(dāng)然你會(huì)發(fā)現(xiàn)如果只有兩個(gè)信號(hào)的組合,就最多表示四個(gè)字,如果想溝通更順暢,我只要增加信號(hào)的組合長(zhǎng)度就可以了,比如三個(gè)信號(hào),我就可以表示八個(gè)字
如果想交流的更加復(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 |
《編碼 隱匿在計(jì)算機(jī)軟硬件背后的語(yǔ)言》《Code: The Hidden Language of Computer Hardware and Software》
一個(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的速度和內(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é)議。
程序真的是按照“順序”執(zhí)行的嗎?
Disorder這個(gè)程序,證明亂序執(zhí)行的確存在
為什么會(huì)亂序?主要是為了提高效率(在等待費(fèi)時(shí)的指令執(zhí)行的時(shí)候,優(yōu)先執(zhí)行后面的指令)
單個(gè)線程,兩條語(yǔ)句,未必是按順序執(zhí)行
單線程的重排序,必須保證最終一致性
as-if-serial:看上去像是序列化(單線程)
多線程會(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í)行完,后面的才能執(zhí)行
intel : lfence sfence mfence(CPU特有指令)
所有實(shí)現(xiàn)JVM規(guī)范的虛擬機(jī),必須實(shí)現(xiàn)四個(gè)屏障
LoadLoadBarrier LoadStore SL SS
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;
我們來(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(); }}
小節(jié)說(shuō)明:
JAVA的6中線程狀態(tài):
如下圖:
線程狀態(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é)難度:低
//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)志位
sleep()方法在睡眠的時(shí)候,不到時(shí)間是沒有辦法叫醒的,這個(gè)時(shí)候可以用interrupt設(shè)置標(biāo)志位,然后呢必須得catch InterruptedException來(lái)進(jìn)行處理,決定繼續(xù)睡或者是別的邏輯,(自動(dòng)進(jìn)行中斷標(biāo)志復(fù)位)
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()
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(); }}
小節(jié)說(shuō)明:
本節(jié)內(nèi)容的重要程度:中(面試有可能被問)
小節(jié)難度:低
結(jié)束線程的方法:
(不重要,暫忽略。)
ThreadGroups - Thread groups are best viewed as an unsuccessful experiment , and you may simply ignore their existence! - Joshua Bloch one of JDK designers
從一個(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)
我們平時(shí)所說(shuō)的'上鎖',一般指的是悲觀鎖
上鎖的本質(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(); }}
CPU級(jí)別匯編,需要查詢匯編手冊(cè)!
Java中的8大原子操作:(了解即可,無(wú)需背過)
重量級(jí)鎖(經(jīng)過操作系統(tǒng)的調(diào)度)synchronized早期都是這種鎖(目前的實(shí)現(xiàn)中升級(jí)到最后也是這種鎖)
輕量級(jí)鎖(CAS的實(shí)現(xiàn),不經(jīng)過OS調(diào)度)(無(wú)鎖 - 自旋鎖 - 樂觀鎖)
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í)間短,等的人少 -> 自旋鎖
JVM 1:1 -> LOOM -> M:N (golang)
(內(nèi)容太多,見另一篇文檔)
有沒有一門編程語(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(); } }
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用于單線程喚醒操作。具體策略是這樣的:
在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ì)于喚醒操作是從等待集的頭部選擇線程喚醒。
通過源碼我們看到,為何總是喚醒線程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í)行。
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ì)象鎖。
這下我們就可以通過以上知識(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ì)被阻塞的。
聯(lián)系客服