歡迎來到《并發(fā)王者課》,本文是該系列文章中的第2篇。
在前面的《兵分三路:如何創(chuàng)建多線程》文章中,我們已經(jīng)通過Thread和Runnable直觀地了解如何在Java中創(chuàng)建一個線程,相信你已經(jīng)有了一定的體感。在本篇文章中,我們將基于前面的示例代碼,對線程做些必要的說明,以幫助你從更基礎(chǔ)的層面認(rèn)知線程,并為后續(xù)的學(xué)習(xí)打下基礎(chǔ)。
一、從進(jìn)程認(rèn)知線程
在上世紀(jì)的80年代中期之前,進(jìn)程一直都是操作系統(tǒng)中擁有資源和獨立運行的基本單位??墒牵S著計算機的發(fā)展,人們對操作系統(tǒng)的吞吐量要求越來越高,并且多處理器也逐漸發(fā)展起來,進(jìn)程作為基本的調(diào)度單位已經(jīng)越來越不合時宜,因為它太重了。
想想看,我們在創(chuàng)建進(jìn)程的時候,需要給它們創(chuàng)建PCB,并且還要分配需要的所有資源,如內(nèi)存空間、I/O設(shè)備等等。然而,在進(jìn)程切換時,系統(tǒng)還需要保留當(dāng)前進(jìn)程的CPU環(huán)境并設(shè)置新的進(jìn)程CPU環(huán)境,這些都是需要花費時間的。也就是說,在進(jìn)程的創(chuàng)建、切換以及銷毀的過程中,系統(tǒng)要花費巨大的時空開銷。如此,在一個操作系統(tǒng)中,就不能設(shè)置過多數(shù)量的進(jìn)程,并且還不能頻繁切換。顯然,這不符合時代的發(fā)展需要了。
因此,進(jìn)程切換的巨大開銷和多核CPU的發(fā)展,線程便順勢而生。
從概念上,線程可以理解為它是操作系統(tǒng)中獨立運行的基本單元,一個進(jìn)程可以擁有多個線程。并且,和進(jìn)程相比,線程擁有進(jìn)程很多相似屬性,因此線程有時候也被稱為輕量級進(jìn)程(Light-Weight Process)。
為什么線程相對輕量
在線程之前,系統(tǒng)切換任務(wù)時需要切換進(jìn)程,開銷巨大。然而,引入線程后,線程隸屬于進(jìn)程,進(jìn)程仍是資源的擁有者,線程只占據(jù)少量的資源。同時,線程的切換并不會導(dǎo)致進(jìn)程的切換,因此開銷較小。此外,進(jìn)程和線程都可以并發(fā)執(zhí)行,操作系統(tǒng)也因此獲得了更好的并發(fā)性,也能有效地提高系統(tǒng)資源的利用率和系統(tǒng)吞吐量。
二、Thread類-Java中的線程基礎(chǔ)
在本文的第一部分,我們從操作系統(tǒng)層面認(rèn)識了線程。而在Java中,我們則需要通過Thread類認(rèn)知線程,包括它的一些基本屬性和基本方法。Thread是Java多線程基礎(chǔ)中的基礎(chǔ),請不要因為它簡單就忽略這部分的內(nèi)容。
下面的這幅圖概括展示了Thread類的核心屬性和方法:

1. Thread中如何構(gòu)造線程
Thread中共有9個公共構(gòu)造器,當(dāng)然我們不用掌握全部的構(gòu)造,熟悉其中幾個比較常用的即可:
-
Thread(),這個構(gòu)造器會默認(rèn)生成一個Thread-+n的名字,n是由內(nèi)部方法nextThreadNum()生成的一個整型數(shù)字; -
Thread(String name),在構(gòu)建線程時指定線程名,是一個很不錯的實踐; -
Thread(Runnable target),傳入Runnable的實例,這個我們在上一篇文章中已經(jīng)展示過; -
Thread(Runnable target, String name),在傳入Runnable實例時指定線程名。
2. Thread中的關(guān)鍵屬性
從init()方法理解Thread的構(gòu)造
雖然Thread有9個構(gòu)造函數(shù),但最終都是通過下面的這個init()方法進(jìn)行構(gòu)造,所以了解了這個方法,就了解了Thread的構(gòu)造過程。
/**
* Initializes a Thread.
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
* @param inheritThreadLocals if {@code true}, inherit initial values for
* inheritable thread-locals from the constructing thread
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals)
init()方法幾十行的代碼,不過為了節(jié)省篇幅,我們此處只貼出了方法的簽名,具體的方法體內(nèi)容可以自行去看。在方法的簽名中,有幾個重要的參數(shù):
-
g:線程所屬的線程組。線程組是一組具有相似行為的線程集合; -
target:繼承Runnable的對象實例; -
name:線程的名字;
其他幾個參數(shù)通常不需要自定義,保持默認(rèn)即可。如果你翻看init()的方法體代碼,可以看到init()對下面幾個屬性做了初始化:
-
name:線程的名字; -
group:線程所屬的線程組; -
daemon:是否為守護進(jìn)程; -
priority:線程的優(yōu)先級; -
tid:線程的ID;
關(guān)于名字
雖然Thread默認(rèn)會生成一個線程名,但為了方便日志輸出和問題排查,比較建議你在創(chuàng)建線程時自己手動設(shè)置名稱,比如anQiLaPlayer的線程名可以設(shè)置為Thread-anQiLa。
關(guān)于線程ID
和線程名一樣,每個線程都有自己的ID,如果你沒有指定的話,Thread會自動生成。確切地說,線程的ID是根據(jù)threadSeqNumber()對Thread的靜態(tài)變量threadSeqNumber進(jìn)行累加得到:
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
關(guān)于線程優(yōu)先級
在創(chuàng)建新的線程時,線程的優(yōu)先級默認(rèn)和當(dāng)前父線程的優(yōu)先級一致,當(dāng)然我們也可以通過setPriority(int newPriority)方法來設(shè)置。不過,在設(shè)置線程優(yōu)先級時需要注意兩點:
- Thread線程的優(yōu)先級設(shè)置是不可靠的:我們可以通過數(shù)字來指定線程調(diào)度時的優(yōu)先級,然而最終執(zhí)行時的調(diào)度順序?qū)⒂刹僮飨到y(tǒng)決定,因為Thread中的優(yōu)先級設(shè)置并不是和所有的操作系統(tǒng)一一對應(yīng);
- 線程組的優(yōu)先級高于線程優(yōu)先級:每個線程都會有一個線程組,我們所設(shè)置的線程優(yōu)先級數(shù)字不能高于線程組的優(yōu)先級。如果高于,將會直接使用線程組的優(yōu)先級。
3. 線程中的關(guān)鍵方法
Thread中幾個重要的方法,如start()、join()、sleep()、yield()、interrupted()等,關(guān)于這幾種方法的用法,我們會在下一篇文章中結(jié)合線程的狀態(tài)進(jìn)行講解。需要注意的是,notify()、wait()等并不是Thread類的方法,它們是Object的方法。
三、多線程的應(yīng)用場景
通過前面的分析,我們已經(jīng)從操作系統(tǒng)層面和Java中認(rèn)知了線程。那么,什么樣的場景需要考慮使用多線程?
總的來說,當(dāng)你遇到以下兩類場景時,需要考慮多線程:
1. 異步
當(dāng)兩個獨立邏輯單元不需要同步順序完成時,可以通過多線程異步處理。
比如,用戶注冊后發(fā)送郵件消息。很顯然,注冊和發(fā)送消息是兩個獨立邏輯單元,在注冊完成后,我們可以另起線程完成消息的發(fā)送,從而實現(xiàn)邏輯解耦并縮短注冊單元的響應(yīng)時間。
2. 并發(fā)
現(xiàn)在的計算機基本都是多核處理器,在處理批量任務(wù)時,可以通過多線程提高處理速度。
比如,假設(shè)系統(tǒng)需要向100萬的用戶發(fā)送消息??梢韵胂?,如果單線程處理不知道猴年馬月才能完成。而此時,我們便可以通過線程池創(chuàng)建多線程大幅提高效率。
注意,對于一些同學(xué)來說,你可能還沒有接觸過多線程的應(yīng)用場景。但是,請不要因為工作中的場景簡單或數(shù)據(jù)量較低就忽視多線程的應(yīng)用,多線程在你身邊的各類中間件和互聯(lián)網(wǎng)大廠中都有著極為廣泛的應(yīng)用。
應(yīng)用多線程時的風(fēng)險提示
雖然多線程有很多的好處,但仍然要根據(jù)場景客觀分析,對多線程不合理的使用會增加系統(tǒng)的安全風(fēng)險和維護風(fēng)險。所以,在使用多線程時,請務(wù)必確認(rèn)場景的合理性,以及它在你技術(shù)能力掌控之中。
以上就是文本的全部內(nèi)容,恭喜你又上了一顆星?
夫子的試煉
- 使用不同的構(gòu)造方式,編寫兩個線程并打印出線程的關(guān)鍵信息;
- 檢索資料,詳細(xì)比對進(jìn)程與線程的區(qū)別。
關(guān)于作者
關(guān)注公眾號【技術(shù)八點半】,及時獲取文章更新。傳遞有品質(zhì)的技術(shù)文章,記錄平凡人的成長故事,偶爾也聊聊生活和理想。早晨8:30推送作者品質(zhì)原創(chuàng),晚上20:30推送行業(yè)深度好文。
如果本文對你有幫助,歡迎點贊、關(guān)注、監(jiān)督,我們一起從青銅到王者。