12. 多線程

寫在之前

以下是《瘋狂Java講義》中的一些知識(shí),如有錯(cuò)誤,煩請(qǐng)指正。

概述

** 進(jìn)程特征**

  • 獨(dú)立性:進(jìn)程是系統(tǒng)中獨(dú)立存在的實(shí)體,它可以擁有自己獨(dú)立的資源,每一個(gè)進(jìn)程都擁有自己私有的地址空間。在沒(méi)有經(jīng)過(guò)進(jìn)程本身允許的情況下,一個(gè)用戶進(jìn)程不可以直接訪問(wèn)其他進(jìn)程的地址空間。
  • 動(dòng)態(tài)性:進(jìn)程與程序的區(qū)別在于:程序只是一個(gè)靜態(tài)的指令集合,而進(jìn)程是一個(gè)正在系統(tǒng)中活動(dòng)的指令集合。在進(jìn)程中加入了時(shí)間的概念。進(jìn)程具有自己的生命周期和各種不同的狀態(tài),這些概念在程序中都是不具備的。
  • 并發(fā)性:多個(gè)進(jìn)程可以在單個(gè)處理器上并發(fā)執(zhí)行,多個(gè)進(jìn)程之間不會(huì)互相影響。

并發(fā)與并行
并行指在同一時(shí)刻,有多條指令在多個(gè)處理器上同時(shí)執(zhí)行;并發(fā)指在同一時(shí)刻只能有一條指令執(zhí)行,但多個(gè)進(jìn)程指令被快速輪換執(zhí)行,使得宏觀上有多個(gè)進(jìn)程同時(shí)執(zhí)行的效果。

多線程編程的優(yōu)勢(shì)

  • 進(jìn)程間不能共享內(nèi)存,但線程之間共享內(nèi)存非常容易。
  • 系統(tǒng)創(chuàng)建進(jìn)程需要為該進(jìn)程重新分配系統(tǒng)資源,但創(chuàng)建線程則代價(jià)小得多,因此使用多線程來(lái)實(shí)現(xiàn)多任務(wù)并發(fā)比多進(jìn)程的效率高。
  • Java語(yǔ)言內(nèi)置的多線程功能支持,而不是單純地作為底層操作系統(tǒng)的調(diào)度方式,從而簡(jiǎn)化了Java的多線程編程

創(chuàng)建線程

**a. 繼承Thread類創(chuàng)建線程類 **

  1. 定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就是代表了線程需要完成的任務(wù)。因此,我們經(jīng)常把run方法稱為線程執(zhí)行體。
  2. 創(chuàng)建Thread子類的實(shí)例,即創(chuàng)建了線程對(duì)象。
  3. 調(diào)用線程對(duì)象的start方法來(lái)啟動(dòng)該線程。
public class FirstThread extends Thread
{
    private int i ;
    // 重寫run方法,run方法的方法體就是線程執(zhí)行體
    public void run()
    {
        for ( ; i < 100 ; i++ )
        {
            // 當(dāng)線程類繼承Thread類時(shí),直接使用this即可獲取當(dāng)前線程
            // Thread對(duì)象的getName()返回當(dāng)前該線程的名字
            // 因此可以直接調(diào)用getName()方法返回當(dāng)前線程的名
            System.out.println(getName() +  " " + i);
        }
    }
    public static void main(String[] args)
    {
        for (int i = 0; i < 100;  i++)
        {
            // 調(diào)用Thread的currentThread方法獲取當(dāng)前線程
            System.out.println(Thread.currentThread().getName()
                +  " " + i);
            if (i == 20)
            {
                // 創(chuàng)建、并啟動(dòng)第一條線程
                new FirstThread().start();
                // 創(chuàng)建、并啟動(dòng)第二條線程
                new FirstThread().start();
            }
        }
    }
}

**b. 實(shí)現(xiàn)Runnable接口創(chuàng)建線程類 **

  1. 定義Runnable接口的實(shí)現(xiàn)類,并重寫該接口的run方法,該run方法的方法體同樣是該線程的線程執(zhí)行體。
  2. 創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例,并以此實(shí)例作為Thread的target來(lái)創(chuàng)建Thread對(duì)象,該Thread對(duì)象才是真正的線程對(duì)象。
  3. 調(diào)用線程對(duì)象的start方法來(lái)啟動(dòng)該線程。
public class SecondThread implements Runnable
{
    private int i ;
    // run方法同樣是線程執(zhí)行體
    public void run()
    {
        for ( ; i < 100 ; i++ )
        {
            // 當(dāng)線程類實(shí)現(xiàn)Runnable接口時(shí),
            // 如果想獲取當(dāng)前線程,只能用Thread.currentThread()方法。
            System.out.println(Thread.currentThread().getName()
                + "  " + i);
        }
    }

    public static void main(String[] args)
    {
        for (int i = 0; i < 100;  i++)
        {
            System.out.println(Thread.currentThread().getName()
                + "  " + i);
            if (i == 20)
            {
                SecondThread st = new SecondThread();     // ①
                // 通過(guò)new Thread(target , name)方法創(chuàng)建新線程
                new Thread(st , "新線程1").start();
                new Thread(st , "新線程2").start();
            }
        }
    }
}

c. 實(shí)現(xiàn)Callable創(chuàng)建多線程

Callable接口,該接口怎么看都像是Runnable接口的增強(qiáng)版,Callable接口也提供了一個(gè)call()方法可以作為線程執(zhí)行體,但call方法比run()方法功能更強(qiáng)大:call()方法可以有返回值;call()可以聲明拋出異常 。
Callable接口有泛型限制,其接口里的泛型形參類型與call方法返回值類型相同。而且Callable接口是函數(shù)式接口,因此可使用Lambda表達(dá)式創(chuàng)建Callable對(duì)象

  1. 定義Callable接口的實(shí)現(xiàn)類,并重寫該接口的call方法,該call方法的方法體同樣是該線程的線程執(zhí)行體。
  2. 創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)例,并將該實(shí)例包裝成FutureTask,F(xiàn)utureTask實(shí)現(xiàn)了Runnable接口。
  3. 將FutureTask實(shí)例作為Thread的target來(lái)創(chuàng)建Thread對(duì)象,該Thread對(duì)象才是真正的線程對(duì)象。
  4. 調(diào)用線程對(duì)象的start方法來(lái)啟動(dòng)該線程。
public class ThirdThread
{
    public static void main(String[] args)
    {
        // 創(chuàng)建Callable對(duì)象
        ThirdThread rt = new ThirdThread();
        // 先使用Lambda表達(dá)式創(chuàng)建Callable<Integer>對(duì)象,無(wú)須先創(chuàng)建Callable實(shí)現(xiàn)類
        // 使用FutureTask來(lái)包裝Callable對(duì)象
        FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
            int i = 0;
            for ( ; i < 100 ; i++ )
            {
                System.out.println(Thread.currentThread().getName()
                    + " 的循環(huán)變量i的值:" + i);
            }
            // call()方法可以有返回值
            return i;
        });
        for (int i = 0 ; i < 100 ; i++)
        {
            System.out.println(Thread.currentThread().getName()
                + " 的循環(huán)變量i的值:" + i);
            if (i == 20)
            {
                // 實(shí)質(zhì)還是以Callable對(duì)象來(lái)創(chuàng)建、并啟動(dòng)線程
                new Thread(task , "有返回值的線程").start();
            }
        }
        try
        {
            // 獲取線程返回值
            System.out.println("子線程的返回值:" + task.get());
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }
}


兩種線程方式的對(duì)比
采用實(shí)現(xiàn)Runnable接口方式的多線程:

  • 線程類只是實(shí)現(xiàn)了Runnable接口,還可以可以繼承其他類。在這種方式下,可以多個(gè)線程共享同一個(gè)target對(duì)象,所以非常適合多個(gè)相同線程來(lái)處理
  • 同一份資源的情況,從而可以將CPU,代碼和數(shù)據(jù)分開,形成清晰的模型,較好地體現(xiàn)了面向?qū)ο蟮乃枷搿?/li>
  • 劣勢(shì)是:編程稍稍復(fù)雜,如果需要訪問(wèn)當(dāng)前線程,必須使用Thread.currentThread()方法。

采用繼承Thread類方式的多線程:

  • 劣勢(shì)是:因?yàn)榫€程類已經(jīng)繼承了Thread類,所以不能再繼承其他父類。
  • 優(yōu)勢(shì)是:編寫簡(jiǎn)單,如果需要訪問(wèn)當(dāng)前線程,無(wú)需使用Thread.currentThread()方法,直接使用this即可獲得當(dāng)前線程。

綜上一般推薦采用實(shí)現(xiàn)Runnable接口的方式來(lái)創(chuàng)建多進(jìn)程。

線程的生命周期

線程狀態(tài)轉(zhuǎn)換

線程死亡
線程會(huì)以以下三種方式之一結(jié)束,一個(gè)結(jié)束后就處于死亡狀態(tài):

  1. run()方法執(zhí)行完成,線程正常結(jié)束。
  2. 線程拋出一個(gè)未捕獲的 Exception或Error。
  3. 直接調(diào)用該線程的stop()方法來(lái)結(jié)束該線程——該方法容易導(dǎo)致死鎖,通常不推薦使用

join線程
Thread提供了讓一個(gè)線程等待另一個(gè)線程完成的方法:join() 方法。當(dāng)在某個(gè)程序執(zhí)行流中調(diào)用其他線程的join()方法時(shí),調(diào)用線程將被阻塞,直到被join方法加入的join線程完成為止。
join()方法通常由使用線程的程序調(diào)用,以將大問(wèn)題劃分成許多小問(wèn)題,每個(gè)小問(wèn)題分配一個(gè)線程。當(dāng)所有的小問(wèn)題都得到處理后,再調(diào)用主線程來(lái)進(jìn)一步操作。

后臺(tái)線程

有一種線程,它是在后臺(tái)運(yùn)行的,它的任務(wù)是為其他的線程提供服務(wù),這種線程被稱為“后臺(tái)線程(Daemon Thread)”,又稱為“守護(hù)線程” 或“精靈線程”。JVM的垃圾回收線程就是典型的后臺(tái)線程。
后臺(tái)線程有個(gè)特征:如果所有的前臺(tái)線程都死亡,后臺(tái)線程會(huì)自動(dòng)死亡。
調(diào)用Thread對(duì)象setDaemon(true)方法可將指定線程設(shè)置成后臺(tái)線程。

線程睡眠
如果我們需要讓當(dāng)前正在執(zhí)行的線程暫停一段時(shí)間,并進(jìn)入阻塞狀態(tài),則可以通過(guò)調(diào)用Thread類的靜態(tài)sleep方法,sleep方法有兩種重載的形式:
static void sleep(long millis):讓當(dāng)前正在執(zhí)行的線程暫停millis毫秒,并進(jìn)入阻塞狀態(tài),該方法受到系統(tǒng)計(jì)時(shí)器和線程調(diào)度器的精度和準(zhǔn)確度的影響。
static void sleep(long millis, int nanos):讓當(dāng)前正在執(zhí)行的線程暫停millis毫秒加nanos毫微妙,并進(jìn)入阻塞狀態(tài),該方法受到系統(tǒng)計(jì)時(shí)器和線程調(diào)度器的精度和準(zhǔn)確度的影響。

線程讓步
yield()方法是一個(gè)和sleep方法有點(diǎn)相似的方法,它也是一個(gè)Thread類提供的一個(gè)靜態(tài)方法,它也可以讓當(dāng)前正在執(zhí)行的線程暫停,但它不會(huì)阻塞該線程。yield只是讓當(dāng)前線程暫停一下,讓系統(tǒng)的線程調(diào)度器重新調(diào)度一次,完全可能的情況是:當(dāng)某個(gè)線程調(diào)用了yield方法暫停之后,線程調(diào)度器又將其調(diào)度出來(lái)重新執(zhí)行。
實(shí)際上,當(dāng)某個(gè)線程調(diào)用了yield方法暫停之后,只有優(yōu)先級(jí)與當(dāng)前線程相同,或者優(yōu)先級(jí)比當(dāng)前線程更高的、就緒狀態(tài)的線程才會(huì)獲得執(zhí)行的機(jī)會(huì)。

sleep方法和yield方法的區(qū)別

  • sleep方法暫停當(dāng)前線程后,會(huì)給其他線程執(zhí)行機(jī)會(huì),不會(huì)理會(huì)其他線程的優(yōu)先級(jí)。但yield方法只會(huì)給優(yōu)先級(jí)相同,或優(yōu)先級(jí)更高的線程執(zhí)行機(jī)會(huì)。
  • sleep方法會(huì)將線程轉(zhuǎn)入阻塞狀態(tài),直到經(jīng)過(guò)阻塞時(shí)間才會(huì)轉(zhuǎn)入就緒狀態(tài)。而yield不會(huì)將線程轉(zhuǎn)入阻塞狀態(tài),它只是強(qiáng)制當(dāng)前線程進(jìn)入就緒狀態(tài)。因此完全有可能某個(gè)線程調(diào)用yield方法暫停之后,立即再次獲得處理器資源被執(zhí)行。
  • sleep方法聲明拋出了InterruptedException異常,所以調(diào)用sleep方法時(shí)要么捕捉該異常,要么顯式聲明拋出該異常。而yield方法則沒(méi)有聲明拋出任何異常。
  • sleep方法比yield方法有更好的可移植性,通常不要依靠yield來(lái)控制并發(fā)線程的執(zhí)行。

線程優(yōu)先級(jí)
每個(gè)線程執(zhí)行時(shí)都有具有一定的優(yōu)先級(jí),優(yōu)先級(jí)高的線程獲得較多的執(zhí)行機(jī)會(huì),而優(yōu)先級(jí)低的線程則獲得較少的執(zhí)行機(jī)會(huì)。
每個(gè)線程默認(rèn)的優(yōu)先級(jí)都與創(chuàng)建它的父線程具有相同的優(yōu)先級(jí),默認(rèn)情況下,main線程的具有普通優(yōu)先級(jí),由main線程創(chuàng)建的子線程也有普通優(yōu)先級(jí)。
Thread提供了setPriority(int newPriority)和getPriority()方法來(lái)設(shè)置和返回指定線程的優(yōu)先級(jí),其中setPriority方法的參數(shù)可以是一個(gè)整數(shù),范圍是1~10之間,也可以使Thread類的三個(gè)靜態(tài)常量:
MAX_PRIORITY:其值是10。
MIN_PRIORITY:其值是1。
NORM_PRIORITY:其值是5。

同步代碼塊
Java的多線程支持引入了同步監(jiān)視器來(lái)解決這個(gè)問(wèn)題,使用同步監(jiān)視器的通用方法就是同步代碼塊。
synchronized后括號(hào)里的obj就是同步監(jiān)視器,上面代碼的含義是:線程開始執(zhí)行同步代碼塊之前,必須先獲得對(duì)同步監(jiān)視器的鎖定。
選擇監(jiān)視器的目的:阻止兩條線程對(duì)同一個(gè)共享資源進(jìn)行并發(fā)訪問(wèn)。因此通常推薦使用可能被并發(fā)訪問(wèn)的共享資源充當(dāng)同步監(jiān)視器。對(duì)于上面的取錢模擬程序,我們應(yīng)該考慮使用賬戶(account)作為同步監(jiān)視器。

同步方法
Java的多線程安全支持還提供了同步方法,同步方法就是使用synchronized關(guān)鍵字來(lái)修飾某個(gè)方法,則該方法成為同步方法。對(duì)于同步方法而言,無(wú)需顯式指定同步監(jiān)視器,同步方法的同步監(jiān)視器是this,也就是該對(duì)象本身。

線程安全的類

  • 不要對(duì)線程安全類的所有方法都進(jìn)行同步,只對(duì)那些會(huì)改變競(jìng)爭(zhēng)資源(競(jìng)爭(zhēng)資源也就是共享資源)的方法進(jìn)行同步。例如上面的Account類中accountNo屬性就無(wú)需同步,所以程序只對(duì)draw方法進(jìn)行同步控制。
  • 如果可變類有兩種運(yùn)行環(huán)境:?jiǎn)尉€程環(huán)境和多線程環(huán)境,則應(yīng)該為該可變類提供兩種版本:線程不安全版本和線程安全版本。在單線程環(huán)境中使用線程不安全版本以保證性能,在多線程環(huán)境中使用線程安全版本。

釋放同步監(jiān)視器
線程會(huì)在如下幾種情況下釋放對(duì)同步監(jiān)視器的鎖定:

  • 當(dāng)前線程的同步方法、同步代碼塊執(zhí)行結(jié)束,當(dāng)前線程即釋放同步監(jiān)視器。
  • 當(dāng)線程在同步代碼塊、同步方法中遇到break、return終止了該代碼塊、該方法的繼續(xù)執(zhí)行,當(dāng)前線程將會(huì)釋放同步監(jiān)視器。
  • 當(dāng)線程在同步代碼塊、同步方法中出現(xiàn)了未處理的Error或Exception,導(dǎo)致了該代碼塊、該方法異常結(jié)束時(shí)將會(huì)釋放同步監(jiān)視器。
  • 當(dāng)線程執(zhí)行同步代碼塊或同步方法時(shí),程序執(zhí)行了同步監(jiān)視器對(duì)象的wait()方法,則當(dāng)前線程暫停,并釋放同步監(jiān)視器。

同步鎖(Lock)
Lock是控制多個(gè)線程對(duì)共享資源進(jìn)行訪問(wèn)的工具。通常,鎖提供了對(duì)共享資源的獨(dú)占訪問(wèn),每次只能有一個(gè)線程對(duì)Lock對(duì)象加鎖,線程開始訪問(wèn)共享資源之前應(yīng)先獲得Lock對(duì)象。不過(guò),某些鎖可能允許對(duì)共享資源并發(fā)訪問(wèn),如 ReadWriteLock(讀寫鎖)。當(dāng)然,在實(shí)現(xiàn)線程安全的控制中,通常喜歡使用ReentrantLock(可重入鎖)。使用該Lock對(duì)象可以顯式地加鎖、釋放鎖。
ReentrantLock鎖具有可重入性,也就是說(shuō)線程可以對(duì)它已經(jīng)加鎖的ReentrantLock鎖再次加鎖,ReentrantLock對(duì)象會(huì)維持一個(gè)計(jì)數(shù)器來(lái)追蹤lock方法的嵌套調(diào)用,線程在每次調(diào)用lock()方法加鎖后,必須顯式調(diào)用unlock()方法來(lái)釋放鎖,所以一段被鎖保護(hù)的代碼可以調(diào)用另一個(gè)被相同鎖保護(hù)的方法

死鎖
當(dāng)兩個(gè)線程相互等待對(duì)方釋放同步監(jiān)視器時(shí)就會(huì)發(fā)生死鎖,Java虛擬機(jī)沒(méi)有監(jiān)測(cè)、也沒(méi)有采用措施來(lái)處理死鎖情況,所以多線程編程時(shí)應(yīng)該采取措施避免死鎖的出現(xiàn)。一旦出現(xiàn)死鎖,整個(gè)程序既不會(huì)發(fā)生任何異常,也不會(huì)給出任何提示,只是所有線程處于阻塞狀態(tài),無(wú)法繼續(xù)。

線程的協(xié)調(diào)運(yùn)行
以借助于Object類提供的wait()、notify()和notifyAll()三個(gè)方法,這三個(gè)方法并不屬于Thread類,而是屬于Object類。但這三個(gè)方法必須同步監(jiān)視器對(duì)象調(diào)用。
關(guān)于這三個(gè)方法的解釋如下:

  • wait():導(dǎo)致當(dāng)前線程等待,直到其他線程調(diào)用該同步監(jiān)視器的notify()方法或notifyAll()方法來(lái)喚醒該線程。該wait()方法有三種形式:無(wú)時(shí)間參數(shù)的wait(一直等待,直到其他線程通知),帶毫秒?yún)?shù)的wait和帶毫秒、微秒?yún)?shù)的wait(這兩種方法都是等待指定時(shí)間后自動(dòng)蘇醒)。調(diào)用wait()方法的當(dāng)前線程會(huì)釋放對(duì)該同步監(jiān)視器的鎖定。
  • notify():?jiǎn)拘言诖送奖O(jiān)視器上等待的單個(gè)線程。如果所有線程都在此同步監(jiān)視器上等待,則會(huì)選擇喚醒其中一個(gè)線程。選擇是任意性的。只有當(dāng)前線程放棄對(duì)該同步監(jiān)視器的鎖定后(使用wait()方法),才可以執(zhí)行被喚醒的線程。
  • notifyAll():?jiǎn)拘言诖送奖O(jiān)視器上等待的所有線程。只有當(dāng)前線程放棄對(duì)該同步監(jiān)視器的鎖定后,才可以執(zhí)行被喚醒的線程。

使用條件變量控制協(xié)調(diào)

當(dāng)使用Lock對(duì)象來(lái)保證同步時(shí),Java提供了一個(gè)Condition類來(lái)保持協(xié)調(diào),使用Condition可以讓那些已經(jīng)得到Lock對(duì)象、卻無(wú)法繼續(xù)執(zhí)行的線程釋放Lock對(duì)象,Condtion對(duì)象也可以喚醒其他處于等待的線程。
Condition 將同步監(jiān)視鎖方法(wait、notify 和 notifyAll)分解成截然不同的對(duì)象,以便通過(guò)將這些對(duì)象與Lock對(duì)象組合使用,為每個(gè)對(duì)象提供多個(gè)等待集(wait-set)。在這種情況下,Lock 替代了同步方法或同步代碼塊,Condition替代了同步監(jiān)視鎖的功能。
Condition實(shí)例實(shí)質(zhì)上被綁定在一個(gè)Lock對(duì)象上。要獲得特定Lock實(shí)例的Condition實(shí)例,調(diào)用Lock對(duì)象newCondition()方法即可。Condtion類提供了如下三個(gè)方法:

  • await():類似于隱式同步監(jiān)視器上的wait()方法,導(dǎo)致當(dāng)前線程等待,直到其他線程調(diào)用該Condtion的signal ()方法或signalAll ()方法來(lái)喚醒該線程。該await方法有更多變體:long awaitNanos(long nanosTimeout)、void awaitUninterruptibly()、awaitUntil(Date deadline)等,可以完成更豐富的等待操作。
  • signal ():?jiǎn)拘言诖薒ock對(duì)象上等待的單個(gè)線程。如果所有線程都在該Lock對(duì)象上等待,則會(huì)選擇喚醒其中一個(gè)線程。選擇是任意性的。只有當(dāng)前線程放棄對(duì)該Lock對(duì)象的鎖定后(使用await()方法),才可以執(zhí)行被喚醒的線程。
  • signalAll():?jiǎn)拘言诖薒ock對(duì)象上等待的所有線程。只有當(dāng)前線程放棄對(duì)該該Lock對(duì)象的鎖定后,才可以執(zhí)行被喚醒的線程。

線程池
系統(tǒng)啟動(dòng)一個(gè)新線程的成本是比較高的,因?yàn)樗婕暗脚c操作系統(tǒng)交互。在這種情形下,使用線程池可以很好地提高性能,尤其是當(dāng)程序中需要?jiǎng)?chuàng)建大量生存期很短暫的線程時(shí),更應(yīng)該考慮使用線程池。
與數(shù)據(jù)庫(kù)連接池類似的是,線程池在系統(tǒng)啟動(dòng)時(shí)即創(chuàng)建大量空閑的線程,程序?qū)⒁粋€(gè)Runnable對(duì)象傳給線程池,線程池就會(huì)啟動(dòng)一條線程來(lái)執(zhí)行該對(duì)象的run方法,當(dāng)run方法執(zhí)行結(jié)束后,該線程并不會(huì)死亡,而是再次返回線程池中成為空閑狀態(tài),等待執(zhí)行下一個(gè)Runnable對(duì)象的run方法。

使用線程池的步驟

  1. 調(diào)用Executors類的靜態(tài)工廠方法創(chuàng)建一個(gè)ExecutorService對(duì)象或ScheduledExecutorService對(duì)象,其中前者代表簡(jiǎn)單的線程池,后者代表能以任務(wù)調(diào)度方式執(zhí)行線程的線程池。
  2. 創(chuàng)建Runnable實(shí)現(xiàn)類或Callable實(shí)現(xiàn)類的實(shí)例,作為線程執(zhí)行任務(wù)。
  3. 調(diào)用ExecutorService對(duì)象的submit方法來(lái)提交Runnable實(shí)例或Callable實(shí)例;或調(diào)用ScheduledExecutorService的schedule來(lái)執(zhí)行線程。
  4. 當(dāng)不想提交任何任務(wù)時(shí)調(diào)用ExecutorService對(duì)象的shutdown方法來(lái)關(guān)閉線程池。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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