面試必問:多線程與線程池

前言

前幾章都在講一些鎖的使用和原理,主要是爲了保證多線程情況下變量的原子性,但這並不是說多線程不好,合理利用還是有好處的。至於什麼好處,看下面內容就懂了,先打個比方吧(誰叫比方,上來捱打):假如你體育考試,要跑1000米,你現在有兩個選擇:

  • 一個人跑完1000米。
  • 找三個人陪你一起跑,每個人跑250米就好

兩種方案你選哪個?

今天寫一下面試必問的內容:多線程與線程池。主要從以下幾方面來說:

  • 什麼是線程(什麼是多線程)
  • 線程狀態
  • 多線程的優點和弊端
  • 線程池的好處
  • 線程池的新建
  • 線程池狀態
  • 線程池執行任務
  • 線程池異常處理
  • 爲什麼submit()方法提交任務產生異常會被"吞掉"

6月6日,好吉利的數字,祝大家六六大順,話不多說,開始搞事!

1、什麼是線程(什麼是多線程)

在這裏先說一下進程,什麼是進程:昨晚我打開的久違的wegame,登錄了英雄聯盟客戶端,而這個客戶端,就是一個進程,客戶端進程名是Client.exe,不信你們試試!

線程是操作系統調度的最小單元,每個線程都擁有各自的計數器、堆棧和局部變量等屬性,並且能夠訪問共享的內存變量。而多個線程組成了一個進程。一個程序,比如愛奇藝客戶端、騰訊客戶端、wegam等,至少有一個進程,而一個進程至少有一個線程。

那麼問題來了,通過一個main方法啓動一個Java程序,這算是進程還是線程呢?代碼如下:

public class ThreadTest {

    public static void main(String[] args) {
        
    }
}

答案是:進程,改進一下上述代碼,執行如下:

public class ThreadTest {

    public static void main(String[] args) {

        // 獲取Java線程管理的MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers)
        // 不需要獲取同步的synchronizer信息,僅獲取線程和線程堆棧晉西
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(truefalse);
        for (ThreadInfo threadInfo : threadInfos){
            System.out.println(
                    "[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName()
            );
        }
    }
}
  • main:main線程,程序入口
  • Reference Handler:清除Reference的線程(對象的引用存在虛擬機棧中,GC的時候需要用到)
  • Finalizer:調用對象finalizer方法的線程(GC的時候需要用到)
  • Signal Dispatcher:分發處理髮送給JVM信號的線程
  • Monitor Ctrl-Break:同步的monitor線程

看上面例子,能清楚的看到,一個Java程序的運行不僅是main()方法的運行,而是main線程和多個其他的線程同時運行,這就是多線程。

多線程:多線程就是指一個進程中同時有多個執行路徑(線程)正在執行

2、線程狀態

Java線程在運行的生命週期中可能處於下表所示的6種不同狀態,在給定的一個時刻,線程只能處於其中一個狀態。

狀態名稱 說明
NEW 初始狀態,線程被構建,但是還沒有調用start()方法
RUNNABLE 運行狀態,Java線程將操作系統中的就緒和運行兩種狀態籠統的成爲“運行中”
BLOCKED 阻塞狀態,表示線程阻塞於鎖(這裏有個容易混淆的問題,下面會講)
WAITING 等待狀態,表示線程進入等待狀態,進入該狀態表示當前線程需要等待其他線程做出一些特定動作(通知或中斷,想下前幾篇文章,進入同步隊列獲取鎖的過程,想想有什麼問題)
TIME_WAITING 超時等待狀態,該狀態不同於WAITING,它是可以在指定的時間自行返回的
TERMINATED 終止狀態,表示當前線程已經執行完畢

注意,在上一篇講ReentrantLock(重入鎖)的時候,當已經有線程獲取了,其餘線程會進入同步隊列,嘗試自旋幾次之後調用LockSupport.park()方法,這個方法是用來阻塞當前線程,那麼在調用了這個方法之後,在同步隊列中線程的狀態是什麼呢?BLOCKED還是WAITING?答案是線程處於WAITING(等待狀態),阻塞狀態是線程阻塞在進入synchronized關鍵字修飾的方法或代碼塊時的狀態,但是阻塞在Lock接口的線程狀態是等待狀態。

線程在自身的生命週期中狀態變遷圖如下所示:

3、多線程的優點和弊端

3.1 優點

  • 多線程技術使程序的響應速度更快(比如打開一個網頁,調用一個接口,這個接口創建了很多線程去數據庫讀取數據異步去返回數據,這樣用戶立馬就打開了網頁,裏面的內容的展現可能不是一起展示,但是總比空白界面停留十幾秒再全部展示出來好吧);

  • 當前沒有進行處理的任務時可以將處理器時間讓給其它任務;

  • 佔用大量處理時間的任務可以定期將處理器時間讓給其它任務;

  • 可以隨時停止任務(中斷某個線程);

  • 可以分別設置各個任務的優先級以優化性能(在線程構建的時候通過setPriority(int)方法來修改優先級,範圍爲1-10,默認優先級爲5,優先級高的線程分配時間片的數量要多於優先級低的線程)。

一個線程在一個時刻只能運行在一個處理器核心上,使用多線程技術,將計算邏輯分配到多個處理器核心上,就會顯著減少程序的處理時間,變得更有效率:

3.2 弊端

  • 等候使用共享資源時造成程序的運行速度變慢(比如庫存,如果多個線程同時去扣減,就有可能變成負數,這樣是不被允許的,所以就需要之前幾篇文章講的鎖來控制,所以前一個線程獲取了資源,後一個線程就會被阻塞,造成程序的運行速度變慢)。

  • 對線程進行管理要求額外的CPU開銷,線程的使用會給系統帶來上下文切換的額外負擔(多線程是通過分配CPU時間片來實現的,時間片非常短,所以CPU會不停的切換線程執行,當一個線程執行一個時間片後會切到下一個任務,但是在切換之前會保存上一個任務的狀態,下次在切換回這個任務的時候,可以再加載這個任務的狀態,這就是上下文切換)。

  • 線程的死鎖。即對共享資源加鎖實現同步的過程中可能會死鎖。

  • 對公有變量的同時讀或寫,可能對造成髒讀等(這就是鎖該做的事情)。

  • 線程創建和銷燬會造成消耗(這就是線程池該做的事情了)。

4、線程池的好處

  • 降低資源消耗(可以通過重複利用已創建的線程來降低線程創建和銷燬造成的消耗)。
  • 提高相應速度(當任務到達時,任務可以不需要等到線程創建就能立即執行)。
  • 提高線程的可管理性(線程是稀缺資源,如果無限制創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控)。

5、線程池的新建

public class ThreadTest {

    /**
     * 基於數組的有界阻塞隊列
     */

    private static ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(10);

    public static void main(String[] args) {
        /**
         * 創建一個線程池
         */

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                // corePoolSize
                5,
                // maximumPoolSize
                10,
                // keepAliveTime
                10L,
                // unit
                TimeUnit.SECONDS,
                // workQueue
                arrayBlockingQueue,
                // threadFactory
                new ThreadFactoryBuilder().setNameFormat("wx-%d").build(),
                // handler
                new ThreadPoolExecutor.AbortPolicy());

        // 上面是創建線程池的代碼,下面只是用來測試拒絕策略的
        for (int i = 0; i < 30; i++){
            threadPoolExecutor.execute(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(5000L);
                }
            });
        }
    }
}
  • corePoolSize:線程池核心線程數大小。
    當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於線程池基本大小時不再創建。

  • maximumPoolSize:線程池最大線程數量。
    線程池允許創建的最大線程數。如果阻塞隊列滿了,並且已創建的線程數小於最大線程數,則線程池會在創建新的線程執行任務(如果使用了無界隊列,那麼這個參數就沒什麼用了)。

  • keepAliveTime:線程池中非核心線程空閒的存活時間大小

  • unit:線程空閒存活時間單位

  • workQueue:存放任務的阻塞隊列
    用於保存等待執行的任務的阻塞隊列。可以選擇以下幾個隊列:
    1、ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按FIFO(先進先出)原則對元素進行排序。
    2、LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO(先進先出)排序元素,吞吐量通常要高於ArrayBlockingQueue。
    3、SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作。否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue。
    4、PriorityBlockingQueue:一個具有優先級的無限阻塞隊列。

  • threadFactory:用於設置創建線程的工廠,可以給創建的線程設置有意義的名字,可方便排查問題,也可以設置線程執行出現異常的處理策略(下面文章會講)
    如上圖,每個線程的name都以wx開頭。

  • handler:線城池的飽和策略事件,主要有四種類型
    1、AbortPolicy:直接拋出異常,如下圖: 2、CallerRunsPolicy:用調用者所在線程來運行任務。
    3、DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
    4、DiscardPolicy:不處理,丟棄掉。

  • 除了上面這四種,還有一種自定義策略,實現RejectedExecutionHandler接口即可:

public class Handler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 打印線程信息
        System.out.println(r.toString());
    }
}

6、線程池狀態

線程池的狀態主要有以下幾種:

狀態名稱 說明
RUNNING 初始狀態,能夠接收新任務,以及對已添加的任務進行處理
SHUTDOWN 線程池處在SHUTDOWN狀態時,不接收新任務,但能處理已添加的任務,處理完成之後纔會退出。
STOP 線程池處在STOP狀態時,不接收新任務,不處理已添加的任務,並且會中斷正在處理的任務。
TIDYING 當所有的任務已終止,線程池會變爲TIDYING狀態。當線程池變爲TIDYING狀態時,會執行鉤子函數terminated()。terminated()在ThreadPoolExecutor類中是空的,若用戶想在線程池變爲TIDYING時,進行相應的處理,可以通過重載terminated()函數來實現。
TERMINATED 線程池執行完鉤子函數terminated()之後,就變成TERMINATED狀態。

這裏有幾個要注意的點:

  • 當調用了shutdown()方法之後,就會從RUNNING轉變爲SHUTDOWN狀態,此時不能再向線程池添加新任務,否則將會拋出RejectedExecutionException異常。
  • 當調用了shutdownNow()方法之後,就會從RUNNING轉變爲STOP狀態,並試圖停止所有正在執行的線程,不再處理還在池隊列中等待的任務,當然,它會返回那些未執行的任務。
  • 當線程池在SHUTDOWN狀態下,阻塞隊列爲空並且線程池中執行的任務也爲空時,就會由SHUTDOWN轉變爲TIDYING狀態。當線程池在STOP狀態下,線程池中執行的任務爲空時,就會由STOP轉變爲TIDYING狀態。
圖片來源:百度搜索
圖片來源:百度搜索

7、線程池執行任務

上面我們創建了一個線程池,並且通過execute()方法提交了任務,然後可以看出上面拋出了異常(拒絕策略), 爲什麼會這樣呢,下面可以看下線程池的處理流程圖就明白了:

線程池處理任務流程:

  • 1: 通過判斷核心線程池裏的線程是否都在執行任務,如果不是,則創建一個線程去執行,如果核心線程都在執行任務。那麼就判斷阻塞隊列。
  • 2: 判斷阻塞隊列是否已滿,如果沒滿,就將任務加到隊列中,如果滿了,就判斷創建的線程是否達到了最大數量(所以這裏有個問題,如果你隊列是無界的,那麼可以一直往裏面添加任務,這就有可能引起內存溢出,這也是阿里官方手冊爲什麼建議用ThreadPoolExecutor去創建線程池了)。
  • 3: ,判斷創建的線程是否達到了最大數量,如果沒有達到,就創建一個線程去執行任務,如果有達到,就執行拒絕策略(默認的拒絕策略是拋出異常,就上面例子拋出的那個異常)。

接下來看下execute()方法的源代碼:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        // 獲取當前有效的線程數和線程池的狀態
        int c = ctl.get();
        // 判斷正在運行線程數是否小於核心線程池,是則新創建一個線程執行任務,否則將任務放到任務隊列中
        if (workerCountOf(c) < corePoolSize) {
            // 在addWorker中創建工作線程執行任務
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 線程池是否處於運行狀態,且是否任務插入任務隊列成功
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 線程池是否處於運行狀態,如果不是則使剛剛的任務出隊
            if (! isRunning(recheck) && remove(command))
                // 執行拒絕策略
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(nullfalse);
        }
        // 插入隊列不成功,且當前線程數數量小於最大線程池數量,此時則創建新線程執行任務,創建失敗的話就執行拒絕策略
        else if (!addWorker(command, false))
            reject(command);
    }

可以看出,execute()方法是沒有返回值的,所以你提交任務之後,是無法判斷任務是否被多線程執行成功,所以多線程還有一種提交方式,submit()方法,通過submit()方法提交任務,線程池會返回一個future類型的對象,通過這個future對象可以判斷任務是否提交成功,,並且可以通過future.get()來獲取返回值,get()方法會阻塞當前線程直到任務完成。實例如下:

public class ThreadTest {

    /**
     * 基於數組的有界阻塞隊列
     */

    private static ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(10);

    public static void main(String[] args) {
        /**
         * 創建一個線程池
         */

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                // corePoolSize
                5,
                // maximumPoolSize
                10,
                // keepAliveTime
                10L,
                // unit
                TimeUnit.SECONDS,
                // workQueue
                arrayBlockingQueue,
                // threadFactory
                new ThreadFactoryBuilder().setNameFormat("wx-%d").build(),
                // handler
                new Handler());

        Future<?> submit = null;
        // 上面是創建線程池的代碼,下面只是用來測試拒絕策略的
        for (int i = 0; i < 10; i++){
            int num = i;
            submit = threadPoolExecutor.submit(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(5000L);
                }
            });
            try {
                submit.get();
            }catch (Exception e){
                System.out.println("線程執行出現異常");
            }
        }
    }
}

線程池異常處理

先來看一段代碼:

public class ThreadTest {

    /**
     * 基於數組的有界阻塞隊列
     */

    private static ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(10);

    public static void main(String[] args) {
        /**
         * 創建一個線程池
         */

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                // corePoolSize
                5,
                // maximumPoolSize
                10,
                // keepAliveTime
                10L,
                // unit
                TimeUnit.SECONDS,
                // workQueue
                arrayBlockingQueue,
                // threadFactory
                new ThreadFactoryBuilder().setNameFormat("wx-%d").build(),
                // handler
                new Handler());

        // 上面是創建線程池的代碼,下面只是用來測試拒絕策略的
        for (int i = 0; i < 10; i++){
            int num = i;
            threadPoolExecutor.submit(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    if (num == 4){
                        throw new RuntimeException();
                    }else {
                        System.out.println(Thread.currentThread().getName());
                        Thread.sleep(5000L);
                    }
                }
            });
        }
    }
}

上述代碼可以看到,當i=4的時候,會拋出一個異常,然後看下結果:

本該打印10行結果的,現在只打印了9行,執行報錯但是沒有拋出異常,這樣我們無法感知任務出現了異常,也就無法做相應處理。

但你把上面代碼的提交方式改爲execute(),再次運行,你會發現有異常拋出的:

這是爲啥子呢,怎麼解決呢,先來說怎麼解決,再說爲啥submit()方法提交任務會將其中可能發生的異常掉。解決方法如下:

  • 添加try/catch捕獲異常
public class ThreadTest {

    /**
     * 基於數組的有界阻塞隊列
     */

    private static ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(10);

    public static void main(String[] args) {
        /**
         * 創建一個線程池
         */

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                // corePoolSize
                5,
                // maximumPoolSize
                10,
                // keepAliveTime
                10L,
                // unit
                TimeUnit.SECONDS,
                // workQueue
                arrayBlockingQueue,
                // threadFactory
                new ThreadFactoryBuilder().setNameFormat("wx-%d").build(),
                // handler
                new Handler());

        // 上面是創建線程池的代碼,下面只是用來測試拒絕策略的
        for (int i = 0; i < 10; i++){
            int num = i;
            threadPoolExecutor.submit(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    try {
                        if (num == 4){
                            throw new RuntimeException();
                        }else {
                            System.out.println(Thread.currentThread().getName());
                            Thread.sleep(5000L);
                        }
                    }catch (Exception e){
                        System.out.println("線程:" + Thread.currentThread().getName() + "執行任務出現了異常");
                    }
                }
            });
        }
    }
}

查看結果:

  • 利用submit()方法返回的future對象的get()方法來查看程序執行是否有異常產生:
public class ThreadTest {

    /**
     * 基於數組的有界阻塞隊列
     */

    private static ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(10);

    public static void main(String[] args) {
        /**
         * 創建一個線程池
         */

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                // corePoolSize
                5,
                // maximumPoolSize
                10,
                // keepAliveTime
                10L,
                // unit
                TimeUnit.SECONDS,
                // workQueue
                arrayBlockingQueue,
                // threadFactory
                new ThreadFactoryBuilder().setNameFormat("wx-%d").build(),
                // handler
                new Handler());

        // 上面是創建線程池的代碼,下面只是用來測試拒絕策略的
        for (int i = 0; i < 10; i++){
            int num = i;
            Future<?> submit = threadPoolExecutor.submit(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    if (num == 4) {
                        throw new RuntimeException();
                    } else {
                        System.out.println(Thread.currentThread().getName());
                        Thread.sleep(5000L);
                    }
                }
            });
            try {
                submit.get();
            }catch (Exception e){
                System.out.println("線程:" + Thread.currentThread().getName() + "執行任務出現了異常");
            }
        }
    }
}

查看結果:

你會發現,它不像上面那個try/catch具體到線程池內那個線程出現了問題,而是說你的主線程執行任務出現了異常

  • 還有一種解決方案,這種異常解決方案是execute()方法提交的任務執行出現異常的處理方式,submit()方法提交的不適用。在定義ThreadFactory的時候,調用setUncaughtExceptionHandler()方法來自定義異常處理方式:
public class ThreadTest {

    private static final Logger logger = LoggerFactory.getLogger(ThreadTest.class);

    /**
     * 基於數組的有界阻塞隊列
     */

    private static ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(10);

    public static void main(String[] args) {
        /**
         * 創建一個線程池
         */

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                // corePoolSize
                5,
                // maximumPoolSize
                10,
                // keepAliveTime
                10L,
                // unit
                TimeUnit.SECONDS,
                // workQueue
                arrayBlockingQueue,
                // threadFactory
                new ThreadFactoryBuilder()
                        // 設置線程名稱
                        .setNameFormat("wx-%d")
                        // 添加自定義異常處理方式:打印error日誌
                        .setUncaughtExceptionHandler((thread, throwable)-> logger.error("ThreadPoolExecutor {} produce exception", thread,throwable))
                        .build(),
                // handler
                new Handler());

        // 上面是創建線程池的代碼,下面只是用來測試拒絕策略的
        for (int i = 0; i < 10; i++){
            int num = i;
            threadPoolExecutor.execute(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    if (num == 4) {
                        throw new RuntimeException();
                    } else {
                        System.out.println(Thread.currentThread().getName());
                        Thread.sleep(5000L);
                    }
                }
            });
        }
    }
}

查看結果:

爲什麼submit()方法提交任務產生異常會被"吞掉"

說到這個問題,我們得先來看下submit()方法的源碼:

public Future<?> submit(Runnable task) {
        if (task == nullthrow new NullPointerException();
        // 任務被包裝成RunnableFuture對象,準備添加到工作隊列中
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

newTaskFor()方法代碼如下:

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

FutureTask類代碼如下:

public class FutureTask<Vimplements RunnableFuture<V{
  ......
}

RunnableFuture接口提供了一個run()方法:

public interface RunnableFuture<Vextends RunnableFuture<V{
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */

    void run();
}

看下FutureTask類的run()方法做了什麼:

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    // 捕獲異常
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

再看下setException()方法:

protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            // 將異常放入outcome對象中
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

至此,我們可以看到,submit()方法其實是將任務包裝成RunnableFuture對象,其實最終是一個FutureTask實例,FutureTask實現了Future和Runnable接口。重寫了run(),而在run()方法裏面,該任務拋出的異常將被捕獲,通過setException()方法將異常放在outcome中,這就是爲什麼沒有拋出異常的原因。

那麼問題來了,爲什麼調用submit()提交任務之後返回的FutureTask對象的get()方法就會看到異常呢,看get()方法源碼:

public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false0L);
        return report(s);
    }

report()方法代碼:

private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

因爲get()方法會將存放異常的outcome對象返回出去,這就是爲什麼調用submit()提交任務之後返回的FutureTask對象的get()方法就會看到異常的原因!

結尾

如果你覺得我的文章對你有幫助話,歡迎關注我的微信公衆號:"一個快樂又痛苦的程序員"(無廣告,單純分享原創文章、已pj的實用工具、各種Java學習資源,期待與你共同進步)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章