JUC編程:線程池的深入

JUC,指的是java util下 concurrent包下面的這些類,通過運用這些類進行併發編程,其中工作中常常瞭解的就是線程池了

1 什麼是線程池

線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然後在創建線程後自動啓動這些任務。線程池線程都是後臺線程。每個線程都使用默認的堆棧大小,以默認的優先級運行,並處於多線程單元中。如果某個線程在託管代碼中空閒(如正在等待某個事件),則線程池將插入另一個輔助線程來使所有處理器保持繁忙。如果所有線程池線程都始終保持繁忙,但隊列中包含掛起的工作,則線程池將在一段時間後創建另一個輔助線程但線程的數目永遠不會超過最大值。超過最大值的線程可以排隊,但他們要等到其他線程完成後才啓動

池化技術有很多應用,例如數據庫連接池,常量池,redis連接池等等,池化技術帶來的好處也是顯而易見的。

2 線程池的運用

線程池的創建有常用的三種方式

1) 固定線程池

固定線程池顧名思義就是線程池中線程數據是固定的

、//創建最大線程數目爲10的固定線程池
xecutorService executorService = Executors.newFixedThreadPool(10);

2)緩存線程池

這是一個可伸縮的線程池,會根據需要不斷創建線程去執行

 ExecutorService executorService = Executors.newCachedThreadPool();

3)單線程

 ExecutorService executorService = Executors.newSingleThreadExecutor();

 其實在工作中,上面這幾種創建方式是不允許使用的,具體可看阿里巴巴開發手冊,其實看源碼就能發現問題

接下來走進源碼,會發現這三種方式的創建都是同一個方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

3 七大參數

corePoolSize:核心線程數,當線程池初始化的時候這些線程就會被初始化,隨時待命。好比我們去銀行辦理業務,總共有十個窗口,但是有三個一直在辦理業務,其他窗口可能會暫時停止服務,這三個就是核心線程數

maximumPoolSize:最大線程數,好比銀行裏面所有的辦理窗口,最大辦理業務就這麼多了。

keepAliveTime:線程存活時間,當超過這個時間任務調度,線程就會被回收。好比銀行窗口超過一個小時沒人辦理,那麼窗口就會掛個牌子告訴你暫停業務辦理,去其他窗口辦理去。這個時間參數也就是在線程總數超過核心線程數的時候起作用,主要針對於那些非核心線程的。

unit:時間單位,沒什麼好說的

workQueue:任務隊列,線程執行的任務都會被扔到這裏。好比你去銀行辦業務,銀行提供的等候區座位,當來一個人辦理業務,當窗口有人辦理時,你就要去等候區等待,線程也一樣,當核心線程被佔用時,任務需提交到隊列中等待。隊列的話後面單獨講,有多重策略實現。

defaultThreadFactory:線程工廠,一般都是用默認生產線程的工廠,具體有什麼差別,後續再說

defaultHandler:拒絕策略。有四種,不過也可以自定義實現,默認的是AbortPolicy,如果不清楚這種策略是什麼意思,來 看源碼

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }

很顯然,,直接拋出異常,這個任務執行不了。其他四種看源碼一看便知(我覺得看源碼學習是最好的學習方式)

怎麼理解這個拒絕策略了。繼續拿上面的例子說:你去銀行辦理業務,十個窗口都滿了,等候區也滿了,那麼你再來銀行就沒地方站了,銀行可能保安會對你說:小夥子,走吧,我們這滿了。

代碼層面理解:當前線程數量大於最大線程數+任務隊列數 則拒絕策略生效,可以代碼實現來證明這個問題:

import java.util.concurrent.*;

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
                       (2,
                       10, 2,
                        TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(10),
                        Executors.defaultThreadFactory(),
                        new ThreadPoolExecutor.AbortPolicy());

        try {
            for (int i = 0; i < 25; i++) {
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("當前線程存活數:" + Thread.activeCount() + ",當前線程名稱:" + Thread.currentThread().getName() + "");
                    }
                });
            }
        } finally {
            //關閉
            threadPoolExecutor.shutdown();
        }
    }
}

打印日誌:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.proxy.demo1.Demo1$1@29453f44 rejected from java.util.concurrent.ThreadPoolExecutor@5cad8086[Running, pool size = 10, active threads = 6, queued tasks = 0, completed tasks = 18]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at com.proxy.demo1.Demo1.main(Demo1.java:18)

多執行幾遍會發現,拒絕策略生效,也就是當任務隊列滿了,最大線程都在忙着,這時候再來任務,拒絕策略就生效了

4 爲什麼工作中需要自定義線程池參數

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

這是創建固定線程池的源碼,會發現任務隊列LinkedBlockingQueue,點進去看他的初始大小,

 /**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}.
     */
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

 可以添加Integer.MAX_VALUE數量級的任務,難道不會引發OOM嗎。

再例如緩存線程池的源碼:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

大家可以看到最大線程數爲Integer.MAX_VALUE,這可不得了啊,無限制的膨脹,不受控制 那還得了。

所以實際項目開發中都是自己配置線程池的參數,參數的配置根據項目的需要來進行配置,一般最大線程數這個值不是固定的,不是代碼中寫死的,可能項目在不同機器上的配置不一樣,cpu核心數也不一樣,所以這個值最好動態獲取

任務呢一般爲兩種:cpu密集型和IO密集型,cpu密集型的話最大線程數爲cpu數量即可,io密集型一般爲cpu最大核心數的兩倍吧,總之要比cpu核心數目多。

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
                       (    
                        //核心線程數,這個根據自身情況定
                        4,
                        //最大線程數,一般用cpu數目,這個可以動態獲取
                        Runtime.getRuntime().availableProcessors(), 
                        //時間
                        2,
                        //時間單位
                        TimeUnit.MICROSECONDS,
                        //任務隊列,記得設置初始大小
                        new LinkedBlockingQueue<>(20),
                        //一般選擇默認的就行
                        Executors.defaultThreadFactory(),
                        //拒絕策略,根據自身需要選擇,如果四種都不滿足,可以自定義實現
                        new ThreadPoolExecutor.AbortPolicy());

總結:線程池的使用最好自定義參數去實現,理解裏面七個參數。其他知識後續講解

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