并發(fā)王者課-青銅02:本來面目-如何簡單認(rèn)識Java中的線程

歡迎來到《并發(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)督,我們一起從青銅到王者。

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

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

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