在科技日益發達的今天,電腦已成為我們工作、學習、娛樂不可或缺的伙伴。然而,面對這個復雜而神秘的電子世界,您是否曾有過想要深入探索其底層奧秘的沖動?是否曾對如何調整硬件參數、優化系統啟動流程感到好奇?這一切,都離不開一個關鍵的“密室”——BIOS(Basic Input/Output System)。今天,潔修哥將化身您的專屬導航員,帶您揭開BIOS的神秘面紗,掌握輕松進入并玩轉它的秘訣。
【小知識】 BIOS,簡單來說,就是連接硬件與操作系統的橋梁,負責初始化硬件設備、檢測系統狀態,并為操作系統提供基本的輸入輸出服務。進入BIOS,您可以調整硬件配置、更改啟動順序、更新固件等,讓電腦更好地適應您的需求。
BIOS通常在電腦啟動的早期階段即可訪問,因此,您需要在開機過程中迅速介入。潔修哥建議您:
- 關閉電腦:確保電腦完全關機,避免任何正在運行的應用程序干擾進入BIOS的過程。
- 準備操作:保持冷靜,牢記接下來的按鍵提示,因為進入BIOS的窗口往往一閃而過。
不同的電腦品牌和型號,進入BIOS的按鍵可能會有所不同。常見的按鍵包括:
- F2鍵
- Delete鍵
- Esc鍵
- F10鍵
- F12鍵
潔修哥提示:部分新型電腦可能使用Fn鍵配合上述功能鍵進入BIOS。例如,某些聯想筆記本需要同時按下Fn + F2。請查閱您的電腦用戶手冊或網上搜索具體型號的進入BIOS方法,確保準確無誤。
在按下正確的按鍵后,電腦會立即跳轉到BIOS界面。此時,請留意屏幕底部或角落出現的提示信息,如“Press F2 to enter Setup”、“BIOS Setup Utility”等字樣,它們會告訴您何時按下指定鍵進入BIOS。
潔修哥提醒:由于進入BIOS的窗口非常短暫(一般只有幾秒鐘),所以請務必眼疾手快,一旦錯過可能需要重啟電腦再試。
成功進入BIOS后,您將面對一個滿屏英文選項的世界。別慌,潔修哥這就為您解讀幾個常用且重要的設置項:
在這里,您可以查看電腦的基本信息,如BIOS版本、CPU型號、內存大小等。這些信息有助于您了解電腦配置,判斷是否需要升級硬件。
此部分包含大量與硬件性能相關的詳細設置,如CPU超頻、內存時序調整、顯卡設置等。對于普通用戶,建議謹慎調整,以免影響系統穩定性。若有專業需求或在專業人士指導下,可適當優化以提升性能。
在此菜單中,您可以設置BIOS密碼,保護電腦免受未經授權的訪問。常見的密碼類型包括:
- Supervisor Password(管理員密碼):用于控制BIOS的所有設置權限。
- User Password(用戶密碼):限制用戶訪問特定功能或修改部分設置。
這是BIOS中最常用的菜單之一,用于設定啟動設備的優先級。例如,如果您想從U盤或光驅啟動安裝系統,只需將相應的設備調整到首位即可。此外,還可以在此處啟用或禁用快速啟動、安全啟動等功能。
完成所有更改后,記得保存并退出BIOS。通常有以下幾種方式:
- Save Changes and Exit(保存更改并退出):保存您所做的修改,然后重啟電腦。
- Discard Changes and Exit(放棄更改并退出):不保存任何修改,直接退出BIOS并重啟。
- Load Optimized Defaults(載入默認設置):恢復BIOS到出廠設置,然后退出。此操作將清除所有自定義設置,謹慎使用。
親愛的讀者,恭喜您跟隨潔修哥的腳步成功解鎖BIOS的大門!從此,您可以更加深入地理解并掌控自己的電腦,無論是調整硬件參數以提升性能,還是更改啟動順序以應對各種應用場景,都變得輕而易舉。記住,每一次對BIOS的探索,都是您向科技達人邁進的堅實步伐。未來的電腦世界,等待您來主宰!
【潔修哥寄語】 科技的魅力,在于探索與實踐。希望每一位讀者都能從今天的教程中收獲知識,享受DIY的樂趣,讓電腦更好地服務于您的生活。我是潔修哥,期待在下一次的科普之旅中與您再見!
有很多民族自豪感爆棚的兄弟會把算盤當成計算機的起源,還有爆破天的兄弟會把陰陽當成二進制0和1的起源,我覺得這件事兒就有點兒不靠譜了
如果非要追究計算機的鼻祖,那就得講講17世紀前歐洲的故事,最早的計算機其實是計算器,就是算數用的,在歐洲工業工業革命的時候,大量的工業模具需要計算,歐洲又沒有中國傳統的計算器 - 算盤,就催生了很多科學家發明自己的計算器(對,就是計算器,就是以前菜市場還在使用的那種,還不能稱之為現在的計算機),這其中有個NB的人物,這個人叫布萊士帕斯卡,我們的壓強單位(帕,千帕,百帕,兆帕)等等,就是以這哥們兒的名字命名,還有,計算機語言里面有一種叫做Pascal,就是為了紀念他。
就是這么個NB的人物,發明了最早的機械計算器,長這樣兒:
經過后人的逐步改進,機械計算機的最后發展堪稱精美,有長這樣兒的:
還有長成這樣兒的:
還有更NB的:
機械計算機改進者中有個人值得一提,他就是德國百科全書式的天才,17世紀的亞里士多德 -- 萊布尼茨!
萊伯尼茲這個人又是個大牛,他既懂物理又懂數學(物理數學不分家),著名的微積分基本定理,牛頓萊布尼茨公式,就是萊布尼茨發明的,當然這里面是牛頓跟萊布尼茨或者說英國跟歐洲大陸的恩怨情仇。簡單說,萊布尼茨發表論文創立微積分公式,牛頓當時是英國皇家學會的老大,話語權影響力比較大。牛頓說萊布尼茨發表的公式是參考了牛頓三年前的筆記,萊布尼茨嗓門不夠響,爭不過牛頓,所以沒有辦法,后人就把這個公式稱為牛頓萊布尼茨公式。
一個人想在社會取得回報或者想發揮巨大作用,就必須要明白這個社會的運行機制,通過這件事兒,大家應該明白話語權的(傳媒、筆桿子)重要性,如果還不能理解,參考美國把上個世紀的美國病毒命名為西班牙病毒這件事兒,當然最近又想把新冠病毒扣在我們腦袋上,就是因為他把控了話語權。衍生出來你應該明白的是,歷史是個任人打扮的小姑娘,你看到的,你聽到的,都是別人想讓你看到和聽到的,所以你要進行深度的思考,他是誰?為什么這么說?他說的是真的嗎?對我有沒有什么企圖?多問自己幾個為什么,你會慢慢從白癡成為智者。
扯遠了,還說回來萊布尼茨,他除了改進機械計算機以外,還有一個重要的發明,那就是大名鼎鼎的二進制!(這里終于跟現代IT技術關聯起來了)據說二進制的發明是參考中國古代的陰陽太極圖而創作出來的,對此,我覺得倒是真的有可能。因為萊布尼茨有一本著名的著作,叫做《論中國人的自然哲學》,說明這個人對中國是有研究的。而且,他發明了二進制以后,還通知了當時的康熙大帝,因為他認為康熙大帝是個數學迷(對此我深表懷疑)。
當然,機械計算機又大又笨重,早就被現代的電子計算機所取代,不過說句題外話,機械計算機也有電子計算機所不具備的優點,就是結實耐用,幾百年都不壞,而且,還不用電 ,誰要是大學食堂里面打飯收費做計算的時候來這么一臺,那絕對是學妹眼中最酷的仔!
順便也來一張現代電子計算機的鼻祖(當然,第一臺電子計算機這件事兒也是見仁見智,美國嗓門大,所以現在資料大多認為1946誕生于美國賓夕法尼亞大學的“ENIAC”是世界上第一臺電子計算機),它長這樣兒:
這是個龐然大物,它大概占地一個別墅,跟一輛前蘇聯虎式坦克一樣重,每個小時耗電150度,但是,每秒鐘的計算量僅區區的5000次,要知道現在手機上的芯片的計算速度可以達到每秒10 0000 0000 0000次。不過就是這樣一臺還比上菜市場計算器的東西,開啟了20世紀最NB的數字化革命!從此之后,計算機行業飛速發展,造就了現在所謂的信息化大革命。
嚴格講,這臺機器應該稱作電子管計算機,因為,這里面用的零件全部都是電子管,電子管如果開關的速度太快,很容易就會壞掉,據說這臺機器每天都會有電子管冒煙兒,工程師在尋找和修復每一個電子管中疲于奔命,想象一天24小時,計算時間僅有半小時,剩下的23個半小時都是在尋找和修復壞掉的點,這是多么讓人抓狂的一件事。如果你不能理解這件事兒,想象一下一個燈泡每秒不停地開關5000次,它會不會壞掉。而且,電子管還有很嚴重的發熱問題,需要把風扇進行緊密的排布,這也是一個工藝難題。
不過,幸運的是,在這臺又笨重毛病又多的計算機問世的第二年,也就是1947年,美國貝爾實驗室研究發明了晶體管,和電子管相比,晶體管體積又小,耗電還低,最重要每秒開關幾十萬上億次都不帶壞的,從這一刻開始,計算機革命才真正的進入了突飛猛進的時代。
這堂課,我們要講的就是計算機的原理。
為什么講線程要講CPU?因為線程和CPU有一對一的對應關系!(超線程除外)
當然,現代的計算機的核心,也就是芯片,是由10 0000 0000 零件構成,我沒有辦法帶你走遍這里面的每一個細節,不過,作為高級語言的程序員,我會帶你走到足夠深的深度,讓你能夠深入理解你寫的每一行代碼到底在計算機內部是怎么轉換成電信號,從而被精密執行的。這一點很重要,因為這會給你帶來“通透感”(原諒我找不到更好的形容詞,現在很多程序員是沒有經過科班訓練的,是根據業務進行速成的,對這樣的小伙伴兒來說,你寫的代碼雖然可以工作,但是它對你是一個黑盒子,你看不到代碼背后的一切,從而也就無法進行更深入的理解和更準確的調優,總之,我個人非常喜歡這種通透感,我不喜歡一個技術對我來說是黑盒,是秘密,希望你也能理解和享受這種通透感)
好吧,讓我們揭開代碼背后的神秘世界吧。
還要從一個故事談起。
我小時候最喜歡的女同學叫小芳,長得好看又善良,我們倆情投意合,每天放學后都約會共同進步,童年的時候山青水白,鳥語花香,環境特別好,我們的年紀都很小,我愛談天她愛笑,有一回并肩坐在桃樹下,風在林梢鳥在叫,不知怎么就睡著了,夢里花落知多少...
不要打斷我,讓我陷在美好的回憶中不可自拔一會兒。
只不過后來大人發現了我們的聯系,用他們自帶的污穢的思想,認為我們的關系是污穢的,是不純潔的,我們當時還沒有羅密歐與朱麗葉,梁山伯與祝英臺這樣的覺悟,不懂得以死相爭,所以就被雙方家長棒打鴛鴦,各自關了禁閉。
不過這個難不倒剛剛學了電學的我,我們就設立了這樣入門級別的電路:
我還發明了燈泡語言:
當然你會發現如果只有兩個信號的組合,就最多表示四個字,如果想溝通更順暢,我只要增加信號的組合長度就可以了,比如三個信號,我就可以表示八個字
如果想交流的更加復雜,我可以增加更長的信號組合,比如我如果用16個長度的信號,就可以表示2^16個漢字,這個數字是65536,要知道,我們日常的漢字常用的話也就4000個左右,整個康熙字典的總字數也僅僅47000個,我用燈泡信號的長度僅需要16個信號長,就足矣涵蓋中文的交流了。
思考題:如果僅需要覆蓋日常交流(4000個漢字),我需要的信號組合的長度至少是多少?
燈泡語言有些復雜,我結合萊布尼茨的二進制,用1來代表燈泡亮(通電),用0來代表燈泡滅(斷電),這樣我和小芳就有了自己的通信語言,比如下面這句話,你猜我說了什么?
111 110 001 000=(? )把答案寫到括號里。
話說到這里,不知道大家有沒有發現,我發明了一種漢字編碼,就是把特定的漢字用0和1的組合表示出來,注意,漢字的編碼并不是只有一種方式,完全有可能發生的是,在一種的編碼方式中,111代表'我',而在另外一種編碼方式中111代表'中',如果我們在解析一段編碼的用錯了編碼格式,就會出現平時經常遇見的'亂碼'問題。
思考題:A編碼中,111=我 110=你,B編碼中 111=沙 110=雕,那么下面這段話究竟代表什么呢?
110 111 110
再有了第一個電路的基礎之上,我有設計了下面的電路:
這里就有了輸入和輸出的概念了
輸入1 | 輸入2 | 輸出 |
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
可以用這樣的符號表示:
也可以有這樣的電路:
輸入1 | 輸入2 | 加和輸出 | 進位輸出 |
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
《編碼 隱匿在計算機軟硬件背后的語言》《Code: The Hidden Language of Computer Hardware and Software》
一個程序,讀入內存,全是0和1構成
從內存讀入到CPU計算,這個時候要通過總線
怎么區分一段01的數據到底是數據還是指令?
總線分類為三種:控制線 地址線 數據線
一個程序的執行,首先把可執行文件放到內存,找到起始(main)的地址,逐步讀出指令和數據,進行計算并寫回到內存。
什么是進程?什么是線程?
一個程序進入內存,被稱之為進程?一個QQ.exe可以運行多份兒嗎?
同一個進程內部:有多個任務并發執行的需求(比如,一邊計算,一邊接收網絡數據,一邊刷新界面)
能不能用多進程?可以,但是毛病多,最嚴重的毛病是,我可以很輕易的搞死別的進程
線程的概念橫空出世:共享空間,不共享計算
進程是靜態的概念:程序進入內存,分配對應資源:內存空間,進程進入內存,同時產生一個主線程
線程是動態的概念:是可執行的計算單元(任務)
一個ALU同一個時間只能執行一個線程
同一段代碼為什么可以被多個線程執行?
保存上下文,保存現場
問題:是不是線程數量越多,執行效率越高?(初級)
展開:調度算法怎么選?(難)
問題:單核CPU多線程執行有沒有意義?(初級)
問題:對于一個程序,設置多少個線程合適?(線程池設定多少核心線程?)(中高級)
線程調度器算法(平均時間片、CFS(考慮權重))
CPU的速度和內存的速度(100 :1)
這里的速度值得是ALU訪問寄存器的速度比訪問內存的速度快100倍
為了充分利用CPU的計算能力,在CPU和內存中間引入緩存的概念(工業上的妥協,考慮性價比)
現在的工業實踐,多采用三級緩存的架構
緩存行:一次性讀取的數據塊
程序的局部性原理:空間局部性 時間局部性
如果緩存行大:命中率高,但讀取效率低。如果緩存行小:命中率低,但讀取效率高。
工業實踐的妥協結果,目前(2021)的計算機多采用64bytes (64 * 8bit)為一行
由于緩存行的存在,我們必須有一種機制,來保證緩存數據的一致性,這種機制被稱為緩存一致性協議。
程序真的是按照“順序”執行的嗎?
Disorder這個程序,證明亂序執行的確存在
為什么會亂序?主要是為了提高效率(在等待費時的指令執行的時候,優先執行后面的指令)
單個線程,兩條語句,未必是按順序執行
單線程的重排序,必須保證最終一致性
as-if-serial:看上去像是序列化(單線程)
多線程會產生不希望看到的結果
ThisEscape(this 溢出問題)
推薦《Effective Java》- 不要在構造方法中啟動線程!
hanppens-before原則(JVM規定重排序必須遵守的規則)
JLS17.4.5 (不需要記住)
?程序次序規則:同一個線程內,按照代碼出現的順序,前面的代碼先行于后面的代碼,準確的說是控制流順序,因為要考慮到分支和循環結構。
?管程鎖定規則:一個unlock操作先行發生于后面(時間上)對同一個鎖的lock操作。
?volatile變量規則:對一個volatile變量的寫操作先行發生于后面(時間上)對這個變量的讀操作。
?線程啟動規則:Thread的start( )方法先行發生于這個線程的每一個操作。
?線程終止規則:線程的所有操作都先行于此線程的終止檢測。可以通過Thread.join( )方法結束、Thread.isAlive( )的返回值等手段檢測線程的終止。
?線程中斷規則:對線程interrupt( )方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生,可以通過Thread.interrupt( )方法檢測線程是否中斷
?對象終結規則:一個對象的初始化完成先行于發生它的finalize()方法的開始。
?傳遞性:如果操作A先行于操作B,操作B先行于操作C,那么操作A先行于操作C
內存屏障是特殊指令:看到這種指令,前面的必須執行完,后面的才能執行
intel : lfence sfence mfence(CPU特有指令)
所有實現JVM規范的虛擬機,必須實現四個屏障
LoadLoadBarrier LoadStore SL SS
volatile修飾的內存,不可以重排序,對volatile修飾變量的讀寫訪問,都不可以換順序
1: volatile i
2: ACC_VOLATILE
3: JVM的內存屏障
屏障兩邊的指令不可以重排!保障有序!
happends-before
as - if - serial
4:hotspot實現
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 用于在多處理器中執行指令時對共享內存的獨占使用。
它的作用是能夠將當前處理器對應緩存的內容刷新到內存,并使其他處理器對應的緩存失效。
另外還提供了有序的指令無法越過這個內存屏障的作用。
DCL單例要不要加volatile?(難)
lock;
我們來認識幾個線程的方法
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,意思就是睡眠,當前線程暫停一段時間讓給別的線程去運行。Sleep是怎么復活的?由你的睡眠時間而定,等睡眠到規定的時間自動復活*/
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,就是當前線程正在執行的時候停止下來進入等待隊列(就緒狀態,CPU依然有可能把這個線程拿出來運行),回到等待隊列里在系統的調度算法里頭呢還是依然有可能把你剛回去的這個線程拿回來繼續執行,當然,更大的可能性是把原來等待的那些拿出一個來執行,所以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, 意思就是在自己當前線程加入你調用Join的線程(),本線程等待。等調用的線程運行完了,自己再去執行。t1和t2兩個線程,在t1的某個點上調用了t2.join,它會跑到t2去運行,t1等待t2運行完畢繼續t1運行(自己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();
}
}
小節說明:
JAVA的6中線程狀態:
如下圖:
線程狀態測試代碼:
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}
* 關于線程狀態的實驗
* @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());
}
}
小節說明:
重要程度:中(面試不多)
小節難度:低
//Thread.java
public void interrupt() //t.interrupt() 打斷t線程(設置t線程某給標志位f=true,并不是打斷線程的運行)
public boolean isInterrupted() //t.isInterrupted() 查詢打斷標志位是否被設置(是不是曾經被打斷過)
public static boolean interrupted()//Thread.interrupted() 查看“當前”線程是否被打斷,如果被打斷,恢復標志位
sleep()方法在睡眠的時候,不到時間是沒有辦法叫醒的,這個時候可以用interrupt設置標志位,然后呢必須得catch InterruptedException來進行處理,決定繼續睡或者是別的邏輯,(自動進行中斷標志復位)
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()不能打斷正在競爭鎖的線程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();
}
}
小節說明:
本節內容的重要程度:中(面試有可能被問)
小節難度:低
結束線程的方法:
(不重要,暫忽略。)
ThreadGroups - Thread groups are best viewed as an unsuccessful experiment , and you may simply ignore their existence! - Joshua Bloch one of JDK designers
從一個簡單的小程序談起:
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=> 競爭條件 , 指的是多個線程訪問共享數據的時候產生競爭
數據的不一致(unconsistency),并發訪問之下產生的不期望出現的結果
如何保障數據一致呢?--> 線程同步(線程執行的順序安排好),
monitor (管程) ---> 鎖
critical section -> 臨界區
如果臨界區執行時間長,語句多,叫做 鎖的粒度比較粗,反之,就是鎖的粒度比較細
具體: 保障操作的原子性(Atomicity)
我們平時所說的"上鎖",一般指的是悲觀鎖
上鎖的本質是把并發編程序列化
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) { //打開注釋試試看,對比結果
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();
}
}
}
同時保障可見性
注意序列化并非其他程序一直沒機會執行,而是有可能會被調度,但是搶不到鎖,又回到Blocked或者Waiting狀態(sync鎖升級)
一定是鎖定同一把鎖(搶一個坑位)
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級別匯編,需要查詢匯編手冊!
Java中的8大原子操作:(了解即可,無需背過)
重量級鎖(經過操作系統的調度)synchronized早期都是這種鎖(目前的實現中升級到最后也是這種鎖)
輕量級鎖(CAS的實現,不經過OS調度)(無鎖 - 自旋鎖 - 樂觀鎖)
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);
運用:
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 >#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "<, " #mp "; je 1f; lock; 1: "
最終實現:
cmpxchg=cas修改變量值
lock cmpxchg 指令
硬件:
lock指令在執行的時候視情況采用緩存鎖或者總線鎖
不同的場景:
臨界區執行時間比較長 , 等的人很多 -> 重量級
時間短,等的人少 -> 自旋鎖
JVM 1:1 -> LOOM -> M:N (golang)
(內容太多,見另一篇文檔)
有沒有一門編程語言天生安全,目前有一門RUST,但是由于語言難度較大,同時缺乏強有力的團隊推廣,目前并不是很流行,對RUST有了解興趣的,參考馬士兵老師《RUST》
public class Test {
/**
* 有三個線程 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");
//啟動線程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");
//啟動線程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
為什么每次運行,線程A總是優先于線程C獲取鎖
在Hotspot源碼中,我們知道synchronized關鍵字是通過monitor_enter和monitor_exit字節來實現的,最終用于阻塞線程的對象為ObjectMonitor對象,該對象包含三個關鍵字段:*WaitSet、*cxq、*EntryList。*WaitSet用于保存使用wait方法釋放獲得的synchronized鎖對象的線程,也即我們調用wait函數,那么當前線程將會釋放鎖,并將自身放入等待集中。而cxq隊列用于存放競爭ObjectMonitor鎖對象失敗的線程,而_EntryList用于也用于存放競爭鎖失敗的線程。那么它們之間有何區別呢?這是由于我們需要頻繁的釋放和獲取鎖,當我們獲取鎖失敗那么將需要把線程放入競爭列表中,當喚醒時需要從競爭列表中獲取線程喚醒獲取鎖,而如果我們只用一個列表來完成這件事,那么將會導致鎖爭用導致CPU資源浪費且影響性能,這時我們獨立出兩個列表,其中cxq列表用于競爭放入線程,而entrylist用于單線程喚醒操作。具體策略是這樣的:
在hotspot中我們稱這種算法為電梯算法,也即將需要喚醒的線程一次性從競爭隊列中放入entrylist喚醒隊列。
那么這時我們就可以分析以上代碼為何總是喚醒線程A了,我們先看線程執行順序,首先啟動線程A,隨后線程A啟動線程B,B線程需要獲取對象鎖從而創建線程C,我們看到當線程A調用wait方法將自己放入等待集中后,將會喚醒線程B,隨后線程B創建并啟動了線程C,然后等待C開始執行,由于此時對象鎖由線程B持有,所以線程C需要放入cxq競爭隊列,隨后B從睡眠中醒來,執行notify方法,該方法總是喚醒了線程A而不是C,也即優先處理等待集中的線程而不是cxq競爭隊列的線程。那么我們通過notify方法來看看實現原理。Notify便是Wait操作的反向操作,所以這里很簡單,無非就是將線程從等待集中移出并且喚醒。源碼如下。
JVM_ENTRY(void, JVM_MonitorNotify(JNIEnv* env, jobject handle))
Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
// 直接調用ObjectSynchronizer::notify
ObjectSynchronizer::notify(obj, CHECK);
JVM_END
這里直接跟進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())) {
// 如果是輕量級鎖,那么直接返回,因為wait操作需要通過對象監視器來做
return;
}
ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
}
可以看到最終調用了ObjectSynchronizer的notify方法來喚醒。源碼如下。
void ObjectMonitor::notify(TRAPS) {
CHECK_OWNER();
if (_WaitSet==NULL) {
// 如果等待集為空,直接返回
return ;
}
int Policy=Knob_MoveNotifyee ; // 移動策略,這里默認是2
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ; // 首先對等待集上自旋鎖
// 調用DequeueWaiter將一個等待線程從等待集中拿出來
ObjectWaiter * iterator=DequeueWaiter() ;
if (iterator !=NULL) {
if (Policy !=4) { // 如果策略不等于4那么將線程的狀態修改為TS_ENTER
iterator->TState=ObjectWaiter::TS_ENTER ;
}
iterator->_notified=1 ; // 喚醒計數器
Thread * Self=THREAD;
iterator->_notifier_tid=Self->osthread()->thread_id();
ObjectWaiter * List=_EntryList ;
if (Policy==0) { // 如果策略為0,那么頭插入到entrylist中
if (List==NULL) { // 如果entrylist為空,那么將當前監視器直接作為_EntryList 頭結點
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隊列
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 {
// 否則直接喚醒線程,讓線程自己去調用enterI進入監視器
ParkEvent * ev=iterator->_event ;
iterator->TState=ObjectWaiter::TS_RUN ;
OrderAccess::fence() ;
ev->unpark() ;
}
}
Thread::SpinRelease (&_WaitSetLock) ; // 釋放等待集自旋鎖
}
這里有一個方法DequeueWaiter() 將線程從等待集中取出來,這里的notify讀者都知喚醒一個,很多人都說隨機喚醒一個,那么我們這里來看看喚醒算法是什么。源碼如下。
inline ObjectWaiter* ObjectMonitor::DequeueWaiter() {
ObjectWaiter* waiter=_WaitSet; // 很簡單對吧,直接從頭部拿
if (waiter) { // 如果waiter不為空,那么從等待集中斷鏈
DequeueSpecificWaiter(waiter);
}
return waiter;
}
inline void ObjectMonitor::DequeueSpecificWaiter(ObjectWaiter* node) {
ObjectWaiter* next=node->_next;
if (next==node) { // 如果只有一個節點,那么直接將等待集清空即可
_WaitSet=NULL;
} else { // 否則雙向鏈表的斷鏈基礎操作
ObjectWaiter* prev=node->_prev;
next->_prev=prev;
prev->_next=next;
if (_WaitSet==node) {
_WaitSet=next;
}
}
// 斷開連接后,也需要把斷下來的節點,next和prev指針清空
node->_next=NULL;
node->_prev=NULL;
}
那么讀者應該可以明顯的看到,底層對于喚醒操作是從等待集的頭部選擇線程喚醒。
通過源碼我們看到,為何總是喚醒線程A,這是用于當線程C競爭不到鎖時,被放入了cxq隊列,而此時entrylist為null,線程A在等待集waitset中,當我們調用notify方法時,由于移動策略默認是2,這時會從等待集的頭部將線程A取下,放入到entrylist中,當notify執行完畢后,在執行后面的monitor_exit字節碼時將會優先從entrylist中喚醒線程,這就導致了A線程總是被優先執行。
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();
}
}
輸出結果:t1 start t2 start t1 end t1 isAlive:true
為什么線程結束了,isAlive方法還返回true
我們首先看看執行流程,線程T1啟動后將會睡眠2秒,隨后2秒后執行結束,隨后線程T2啟動,T2首先獲取到T1的對象鎖,然后睡眠5秒,隨后調用T1的isAlive方法判定線程是否存活,那么為什么會輸出true呢?我們還得先看看isAlive方法如何實現的。我們來看源碼。
public final native boolean isAlive();
首先看到isAlive方法由JNI方法實現。我們來看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轉為oop對象thread_oop,隨后調用java_lang_Thread的is_alive方法來判斷是否存活,我們繼續跟進。
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對象,也即java的Thread類中的eetop屬性,如果該屬性為null,那么表明線程已經銷毀,也即返回false,如果eetop還在那么返回true,表明線程存活。那么什么是eetop呢?我們還得從線程創建方法入手。
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread *native_thread=NULL;
bool throw_illegal_thread_state=false; // 非法線程狀態標識
{
// Threads_lock上鎖,保證C++的線程對象和操作系統原生線程不會被清除。當前方法執行完,也就是棧幀釋放時,會釋放這里的鎖,當然肯定會調用析構函數,而這個對象的析構函數中調用unlock方法釋放鎖
MutexLocker mu(Threads_lock);
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) !=NULL) { // 如果線程不為空,則表明線程已經啟動,則為非法狀態
throw_illegal_thread_state=true;
} else {
// 本來這里可以檢測一下stillborn標記來看看線程是否已經停止,但是由于歷史原因,就讓線程自己玩了,這里就不玩了
// 取得線程對象的stackSize的大小
jlong size=java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
// 開始創建C++ Thread對象和原生線程對象,使用無符號的線程棧大小,所以這里不會出現負數
size_t sz=size > 0 ? (size_t) size : 0;
// 創建JavaThread,這里的thread_entry為傳入的運行地址,也就是啟動線程,需要一個入口執行點,這個函數地址便是入口執行點
native_thread=new JavaThread(&thread_entry, sz);
// 如果osthread不為空,則標記當前線程還沒有被使用
if (native_thread->osthread() !=NULL) {
native_thread->prepare(jthread);
}
}
}
// 如果throw_illegal_thread_state不為0,那么直接拋出異常
if (throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}
// 原生線程必然不能為空,因為線程是由操作系統創建的,所以沒有OS線程,空有個JavaThread類有啥用0.0
if (native_thread->osthread()==NULL) {
delete native_thread; // 直接用C++的delete釋放內存
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),"unable to create new native thread");
}
Thread::start(native_thread); // 一切準備妥當,開始啟動線程
JVM_END
我們看到首先創建了JavaThread對象,該對象內部創建了OSThread對象,我們這么理解:JavaThread代表了C++層面的Java線程,而OSThread代表了操作系統層面的線程對象。隨后調用了native_thread->prepare(jthread)方法為啟動線程做準備。我們關注該方法。
void JavaThread::prepare(jobject jni_thread, ThreadPriority prio) {
// 包裝當前Java線程對象
Handle thread_oop(Thread::current(),
JNIHandles::resolve_non_null(jni_thread));
// 將Java層面的線程Oop對象與JavaThread C++層面的對象關聯
set_threadObj(thread_oop());
java_lang_Thread::set_thread(thread_oop(), this);
// 設置優先級
if (prio==NoPriority) {
prio=java_lang_Thread::priority(thread_oop());
}
Thread::set_priority(this, prio);
// 將JavaThread類放入到全局線程列表中
Threads::add(this);
}
我們注意看 java_lang_Thread::set_thread方法。我們跟進它的源碼。
void java_lang_Thread::set_thread(oop java_thread, JavaThread* thread) {
// 將JavaThread C++層面的線程對象設置為Java層面的Thread oop對象的eetop變量
java_thread->address_field_put(_eetop_offset, (address)thread);
}
這下我們知道了eetop變量即使JavaThread對象的地址信息。在了解完eetop如何被設置之后我們得繼續看,eetop什么時候被取消。當Java線程執行完Runnable接口的run方法最后一個字節碼后,將會調用exit方法。該方法完成線程對象的退出和清理操作,我們重點看ensure_join方法。
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
...
ensure_join(this);
...
}
我們繼續跟進ensure_join的源碼實現。
static void ensure_join(JavaThread* thread) {
// 封裝Java Thread線程oop對象
Handle threadObj(thread, thread->threadObj());
// 獲取Java Thread線程oop對象鎖
ObjectLocker lock(threadObj, thread);
// 清除未處理的異常信息
thread->clear_pending_exception();
// 將狀態修改為TERMINATED
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// 將Java Thread線程oop對象與JavaThread C++對象解綁
java_lang_Thread::set_thread(threadObj(), NULL);
// 喚醒所有阻塞在線程對象的線程
lock.notify_all(thread);
// 如果以上代碼期間發生異常,那么清理掛起的異常
thread->clear_pending_exception();
}
我們看到最終由ensure_join方法中的java_lang_Thread::set_thread(threadObj(), NULL),將eetop變量設置為null,當執行完這一步時,我們再通過isAlive方法判斷線程是否存活時,將返回false,否則返回true。而我們看到在操作該變量時需要獲取線程對象鎖。我們來看ObjectLocker的構造函數和析構函數的實現。
ObjectLocker::ObjectLocker(Handle obj, Thread* thread, bool doLock) {
_dolock=doLock;
_thread=thread;
if (_dolock) {
// 獲取Java Thread線程oop對象鎖
ObjectSynchronizer::fast_enter(_obj, &_lock, false, _thread);
}
}
ObjectLocker::~ObjectLocker() {
if (_dolock) {
// 釋放Java Thread線程oop對象鎖
ObjectSynchronizer::fast_exit(_obj(), &_lock, _thread);
}
}
我們看到當我們創建ObjectLocker對象時,會在構造函數中獲取到線程對象鎖,而當ensure_join方法執行完畢后,將會調用ObjectLocker的析構函數,在該函數中釋放線程對象鎖。
這下我們就可以通過以上知識來分析為何isAlive方法在線程執行完畢后仍然返回true了,這是用于isAlive方法通過判斷Java線程對象的eetop變量來判定線程是否存活,而當我們線程執行完畢后將會調用exit方法,該方法將會調用ensure_join方法,在該方法中將eetop甚至為null,但是由于賦值前需要獲取到Java線程的對象鎖,而該對象的對象鎖已經由線程T2持有,這時當前線程將會阻塞,從而造成eetop變量沒有被清除,從而導致isAlive方法在T1線程執行完畢后仍然返回true。讀者也可以看看java Thread的源碼,join函數也是通過對Thread對象獲取鎖然后調用isAlive來判定線程是否結束的,這就意味著如果我們用別的線程持有了Java Thread的對象鎖,那么這時調用join方法的線程也是會被阻塞的。
暑期創作大賽#
作者:洞見pumpkin
山不過來,我就過去。
吳軍博士曾在演講中說:向上的路注定艱難,而向下的大門永遠敞開。
一個人努力向上的過程,就像在攀登一座金字塔。
向上,步履維艱;向下,萬丈深淵。
一旦你下定決心向上突破,就會有很多向下的力量,拉扯你,牽引你。
這些有形的無形的,把你往下拽的力量,我稱之為“底層牽引力”。
01
原生家庭引力
知乎上有個熱門問題:貧窮,為什么會遺傳?
評論區有兩個真實的故事。
答主@曹執的父親16歲參軍,走出蘇北農村,參加過對越自衛反擊戰,年紀輕輕干到副營級。卻在前途一片大好的時候,轉業回家了。
理由是奶奶說家里的地沒人種,于是他父親聽了奶奶的話,回家種地了。
另一名匿名用戶說,他小時候窮到交不起學費,大學誤打誤撞被調劑到了計算機系。
畢業時通過了騰訊的網申,卻在面試的時候主動放棄了。因為他買不起車票,家里人讓他選擇縣城月薪3000的國企。
多年后,當初和他一起面試的同學,早已財務自由,他還在縣城的國企數著薪水,算著房貸。
一旦你有跨越階層的機會,第一個拖住你的不是旁人,而是原生家庭。
你想去大城市闖一闖時,親戚朋友勸你:女孩子別想東想西,趕快找人嫁了吧。
你想換一份更有前景的工作,父母不支持:現在穩定的工作多難得,別瞎折騰。
你想創業做生意,家里人第一個反對:創業風險太高了,別冒險了。
倒不是身邊的人見不得你好,而是認知有限。
大西洋月刊曾發表過一篇文章,描述了這樣一個畫面。
你站在社會經濟的梯子上,腳踝上捆著一根橡皮筋,皮筋的另一頭捆在你父母所站的梯級。
如果你的父母站在梯子的上端,當你往下掉時橡皮筋會把你拉回來;
如果他們站在下端,你開始向上爬時它就會把你往下拽。
所以,如果你出身普通,你要趁早明白,你的原生家庭給你的建議,本質上都會把你拉到和他們同等階層稍高一點的位置。
如果不努力沖破這個枷鎖,那你的人生很可能就是他們的2.0版本。
02
奶頭樂引力
作家霧滿攔江認為讀書有五個層次:
第一層:純娛樂小說,讀自己喜歡讀的書。
第二層:傳統經典小說,讀對自己有用的書。
第三層:進入史哲領域,讀有價值的書。
第四層:進入思想領域,讀有延展性的書。
第五層:就是形成自己的思想體系,并依據自我體系構建新的閱讀書目。
簡單來講,一個人閱讀的深度,就是他思想的尺度,高層次的人,往往都是深度閱讀者。
看過一幅令人深思的漫畫。
互聯網高速發展,人們點點鼠標,動動手指,就能輕松獲取各種信息。
跳著手指舞的帥哥美女應接不暇,娛樂圈的明星八卦一波接一波,驚險刺激的網絡游戲讓人欲罷不能,速成的付費學習緩解了知識焦慮。
你喜歡什么,手機最清楚;你愛好什么,算法就給你推送什么。
于是,人成了互聯網寵物,活在自己的信息繭房中。
思考力成了時代的稀缺品,注意力渙散成了時代病癥。
你有沒有發現:自己能夠專注思考的時間正在越變越短,自己能夠全力以赴的事情正在越變越少。
你很難完整地讀完一本書,原速看完一部紀錄片,獨立組織一篇文稿。
長期浸泡在“短平快”的低密度碎片化信息中,失去了思考力,就失去了進步的可能。
沉迷于這種低級的“奶頭樂”中,失去進取心,就會永遠被桎梏在底層。
有一句話我非常相信:You are what you read.
直譯是:你讀什么,你就會成為什么。
更好的翻譯是:一個人獲得什么樣的信息,往往決定了他成為什么樣的人。
不要讓你的時間和注意力都被網絡劫持,養成深度閱讀的好習慣。
我們才不會淪為信息的傀儡,快餐信息的回收站。
03
底層圈子引力
《經濟學人》在做一個調查時,發現了一個非常反常的現象:
中產階級和貧困階層的工作時間,在最近30年出現了大幅下降;
而富人和精英階層的工作時間,卻在最近30年出現了大幅提升。
50%以上的億萬富翁,每周的工作時間超過65個小時。
為什么?
富人認為工作的本質是為自己創造價值,所以堅持在工作中提升技能。
窮人認為工作的本質是為老板提供價值,所以“摸魚”成了反抗資本最有力的武器。
于是乎上班摸魚成了政治正確,努力工作的人,就被打上了“奮斗逼”的標簽,被孤立被嘲諷。
卡內基鋼鐵公司董事長齊瓦勃,18歲時在卡內基經營的一家建筑公司打工。
工友們抱怨工作累、薪水低,工作時敷衍對付。
齊瓦勃卻熱情滿滿,他立志成為優秀員工,上班勤勤懇懇,下班還自學建筑學的相關知識。
工友們看不慣他這樣的異類,紛紛挖苦嘲笑他,他不管不顧,熱情依舊。
八年后,他升任總工程師,年僅25歲,就成了這家公司的總經理。
而當初那些嘲諷他的工友,要么被裁,要么還停留在原來的崗位。
工作中,總有人在敷衍、摸魚,而且設法將你變為他們的一員。
我勸你一定要離這種人遠一點。
經濟學教授薛兆豐說過一句話:人,永遠在為自己的簡歷打工。
你在工作中混日子,工作遲早有一天會淘汰你。
04
弱者價值觀引力
大V“科學未來人”說過一句話:
有個真實感受不知道該不該講,劇烈的社會分層,儼然正成為自我實現的預言。
就是我們身邊的好多人,都在拼命學努力做,于是他們物質財富、社會地位,都在肉眼可見地快速積累。
但還有一部分人,總是埋怨階層固化、沒有前途,覺得什么都是別人想割我的韭菜,把“弱者的武器”工具箱打開,全用在抱怨、仇恨、躺平、擺爛上,于是他們的生活真的變得很爛。
你身邊肯定有這樣的人:
看見富二代們炫富,就抱怨出身不公;失意落魄時,就埋怨階級固化;看到比自己資歷淺的同事升遷,就揣測為某種潛規則。
把自己的失敗,不如意甩鍋給社會和別人,固然能聊以自慰。
但短暫精神馬殺雞的代價是,沉浸于自憐自抑的情緒中失去了自救的可能性。
一位心理學把人的價值觀分為兩類:一類叫弱勢價值觀,一類叫強勢價值觀。
弱勢價值觀遇事問:憑什么?喜歡向外歸因。
強勢價值觀卻習慣于問自己:為什么?善于向內求因。
就像出身農民的俞敏洪,高中時候,他的老師對全班同學說:
你們在座的,沒有一個能考上大學,以后一定都是農民。
許多學生相信了,不是中途輟學,就是沒考上就放棄了。
俞敏洪不認命,考一次沒考上就考兩次,兩次沒考上就考三次。
最后他終于考上了北大,一個農村娃,成了企業家。
《古蘭經》中講:山不過來,我就過去。
人生而不平等,出身的差距,天賦的差距,資源的差距……
抱怨毫無意義,行動才有轉機。
當你把“憑什么”的抱怨,換成“為什么”的自救時,就意味著你又向高處移動了一大步。
05
多巴胺引力
生物學上有一個著名的實驗,叫作“老鼠也瘋狂”。
一只老鼠被連上了一個電極,每當它按下按鈕,就有微小的電流刺激它的大腦。
但老鼠被電后不僅沒有躲開,反而越來越興奮。
12個小時瘋狂按了7000次按鈕,最后力竭而死。
原來,電極刺激了大腦區域的快感中樞,會產生讓人興奮的多巴胺,于是老鼠掉入了這個“快感陷阱”里。
別嘲笑老鼠,其實我們高明不了多少。
知道垃圾食品有害健康,卻忍不住大快朵頤;
深知短視頻讓人上癮,卻忍不住打開刷個不停。
一根網線,一個外賣電話,一屏永遠看不完的短視頻,可以讓你在短時間享受到快樂。
但所有能立馬滿足你的娛樂方式,最終都會在不知不覺中徹底毀了你。
無節制的爽感之后,往往是無盡的空虛。
哈佛商學院做過一個調查,結果發現了一個反差極大的現象:
越是富人越是精英階層,越喜歡采用補充型的娛樂方式:比如健身、閱讀、學習。
而越是窮人越是底層的人,越喜歡采用消耗型的娛樂方式:比如打牌、打游戲、看肥皂劇。
永遠不要讓自己成為那只瘋狂的老鼠。
低級的快樂靠放縱,頂級的快樂靠自律。
讀書很枯燥,但一頁一頁啃,才能內化為知識和智慧;
健身很痛苦,但汗一滴一滴流,才能塑造更強健的身體;
多巴胺只能帶來片刻的歡愉,苦熬過后內啡肽帶來的快樂,才有持續一生的回甘。
▽
《奇葩說》里辯手肖驍有一句辯詞我特別喜歡:
往往最誘惑的選擇,不是上帝給你的機會,而是惡魔給你的考題。
在向上攀緣的路上,我們會遇到無數這樣的選擇題:
勇敢還是穩定?舒適還是痛苦?安逸還是改變?
我只有一個建議:在人生的十字路口,永遠選擇困難模式。
因為好走的路,都是下坡路。
點個贊吧,與朋友們共勉。