乾貨,談談對java線程池的理解(面試必備)

線程池的概念

線程池由任務隊列和工作線程組成,它可以重用線程來避免線程創建的開銷,在任務過多時通過排隊避免創建過多線程來減少系統資源消耗和競爭,確保任務有序完成。

類的繼承關係
ThreadPoolExecutor 繼承自 AbstractExecutorService 實現了 ExecutorService 接口,ScheduledThreadPoolExecutor 繼承自 ThreadPoolExecutor 實現了 ExecutorService 和 ScheduledExecutorService 接口。

ThreadPoolExecutor構造方法

      //有多個構造方法,最終都指向這個最多參數的構造方法
      public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory) {
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 threadFactory, defaultHandler);
        }

corePoolSize:核心運行的線程個數,也就是當超過這個範圍的時候就需要將新的異步任務放入到等待隊列中,小於這個數時添加進來的異步任務一般直接新建Thread 執行;

maximumPoolSize:最大線程個數,當大於了這個值就會將準備新加的異步任務由一個丟棄處理機制來處理,大於 corePoolSize 且小於 maximumPoolSize 則新建 Thread 執行,但是當通過newFixedThreadPool 創建的時候,corePoolSize 和 maximumPoolSize 是一樣的,而corePoolSize 是先執行的,所以他會先被放入等待隊列而不會執行到下面的丟棄處理中;

workQueue:任務等待隊列,當達到 corePoolSize的時候就向該等待隊列放入線程信息(默認爲一個LinkedBlockingQueue);

keepAliveTime:默認是0,當線程沒有任務處理後空閒線程保持多長時間,不推薦使用;

threadFactory:是構造 Thread 的方法,一個接口類,可以使用默認的 default實現,也可以自己去包裝和傳遞,主要實現 newThread 方法即可;

defaultHandler:當參數 maximumPoolSize 達到後丟棄處理的方法實現,java 提供了 5種丟棄處理的方法,當然也可以自己弄,主要是要實現接口 RejectedExecutionHandler 中rejectedExecution(Runnabler, ThreadPoolExecutor e) 方法,java 默認使用的是AbortPolicy,他的作用是當出現這種情況的時候拋出一個異常;通常得到線程池後會調用其中的 submit 或 execute 方法去提交執行異步任務,其實 submit 方法最終會調用execute 方法來進行操作,只是他提供了一個 Future
來託管返回值的處理而已,當你調用需要有返回值的信息時用它來處理是比較好的,這個 Future 會包裝 Callable 信息。

BlockingQueue有四個具體的實現類,根據不同需求,選擇不同的實現類
1、ArrayBlockingQueue:一個由數組支持的有界阻塞隊列,規定大小的BlockingQueue,其構造函數必須帶一個int參數來指明其大小.其所含的對象是以FIFO(先入先出)順序排序的。
2、LinkedBlockingQueue:大小不定的BlockingQueue,若其構造函數帶一個規定大小的參數,生成的BlockingQueue有大小限制,若不帶大小參數,所生成的BlockingQueue的大小由Integer.MAX_VALUE來決定.其所含的對象是以FIFO(先入先出)順序排序的。
3、PriorityBlockingQueue:類似於LinkedBlockQueue,但其所含對象的排序不是FIFO,而是依據對象的自然排序順序或者是構造函數的Comparator決定的順序。
4、SynchronousQueue:特殊的BlockingQueue,對其的操作必須是放和取交替完成的。
LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的話,默認最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在隊列滿的時候會阻塞直到有隊列成員被消費,take方法在隊列空的時候會阻塞,直到有隊列成員被放進來。

看個線程池的例子

public class ThreadPoolTest {
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = Math.max(4, Math.min(CPU_COUNT - 1, 5));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 2;
    private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(10);

 //一共執行20個任務 ,核心線程數是4,最大核心線程數是10,目前加入的runnable20個(相當於20個任務),
 //20個任務需要執行,但是核心線程數只有4個,還有16個任務,由於LinkedBlockingQueue隊列是最大存放的任務爲10   個,隊列滿了,則會創建新的線程去執行任務,這個時候最大線程是10, 非核心線LinkedBlockingQueue數還有6個,這時候會開6個線程去執行,  目前達到10個最大線程數,此時隊列裏面還有10個。正好滿足隊列的大小

    static {
        System.out.println("核心線程數=" + CORE_POOL_SIZE);
        System.out.println("最大線程數=" + MAXIMUM_POOL_SIZE);

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,  //核心線程數
                MAXIMUM_POOL_SIZE, //線程池中最大的線程數
                60,  //線程的存活時間,沒事幹的時候,空閒的時間
                TimeUnit.SECONDS, //線程存活時間的單位
                sPoolWorkQueue, //線程緩存隊列
                new ThreadFactory() {  //線程創建工廠,如果線程池需要創建線程會調用newThread來創建
                    @Override
                    public Thread newThread(@NonNull Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setDaemon(false);
                        return thread;
                    }
                });
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("執行完畢" + Thread.currentThread().getName());
                }

            };
            //丟給線程池去執行
            THREAD_POOL_EXECUTOR.execute(runnable);
        }
    }
}

代碼具體思路:
一共執行20個任務 ,核心線程數是4,最大核心線程數是10,目前加入的runnable20個(相當於20個任務),20個任務需要執行,但是核心線程數只有4個,還有16個任務,由於LinkedBlockingQueue隊列是最大存放的任務爲10 個,隊列滿了,則會創建新的線程去執行任務,這個時候最大線程是10, 非核心線LinkedBlockingQueue數還有6個,這時候會開6個線程去執行, 目前達到10個最大線程數,此時隊列裏面還有10個。正好滿足隊列的大小。

運行結果
在這裏插入圖片描述
在看個例子

    public class ThreadPoolTest {
        private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        private static final int CORE_POOL_SIZE = Math.max(4, Math.min(CPU_COUNT - 1, 5));
        private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 2;
        private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(9);
    
    
        //一共執行20個任務 ,核心線程數是4,最大核心線程數是10,目前加入的runnable20個(相當於20個任務),
        //20個任務需要執行,但是核心線程數只有4個,還有16個任務,由於LinkedBlockingQueue隊列是最大存放的任務爲9個,隊列滿了,則會創建新的線程去執行任務
        //這個時候最大線程是10,非核心線程數還有6個,這時候會開6個線程去執行,目前達到10個最大線程數,此時隊列裏面最大隻能存放9個。
        //還有一個Runnable,此時就會報錯RejectedExecutionException
    
        static {
            System.out.println("核心線程數=" + CORE_POOL_SIZE);
            System.out.println("最大線程數=" + MAXIMUM_POOL_SIZE);
    
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                    CORE_POOL_SIZE,  //核心線程數
                    MAXIMUM_POOL_SIZE, //線程池中最大的線程數
                    60,  //線程的存活時間,沒事幹的時候,空閒的時間
                    TimeUnit.SECONDS, //線程存活時間的單位
                    sPoolWorkQueue, //線程緩存隊列
                    new ThreadFactory() {  //線程創建工廠,如果線程池需要創建線程會調用newThread來創建
                        @Override
                        public Thread newThread(@NonNull Runnable r) {
                            Thread thread = new Thread(r);
                            thread.setDaemon(false);
                            return thread;
                        }
                    });
            threadPoolExecutor.allowCoreThreadTimeOut(true);
            THREAD_POOL_EXECUTOR = threadPoolExecutor;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 20; i++) {
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("執行完畢" + Thread.currentThread().getName());
                    }
    
                };
                //丟給線程池去執行
                THREAD_POOL_EXECUTOR.execute(runnable);
            }
        }
    }

代碼具體思路:
一共執行20個任務 ,核心線程數是4,最大核心線程數是10,目前加入的runnable20個(相當於20個任務),20個任務需要執行,但是核心線程數只有4個,還有16個任務,由於LinkedBlockingQueue隊列是最大存放的任務爲9個,隊列滿了,則會創建新的線程去執行任務,這個時候最大線程是10,非核心線程數還有6個,這時候會開6個線程去執行,目前達到10個最大線程數,此時隊列裏面最大隻能存放9個,所以線程池會拋異常!

運行結果
在這裏插入圖片描述
這個例子就是把LinkedBlockingQueue的大小改爲了9個,大家可以清楚的知道RejectedExecutionException 報錯的原因,其實也是AsyncTask存在的隱患,如下圖
在這裏插入圖片描述比如去執行200個任務,AsyncTask肯定會報錯!

相信大家看例子的同時在結合概念會很清楚的理解和使用java線程池。

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