你不得不瞭解的JAVA線程池!

阿里巴巴規範說過,使用線程最好是用線程池,那就是說使用線程池有一定的好處,能夠管理線程連接,開啓用戶使用的線程數,用完迴歸池中,可以讓其他線程使用,減少連接線程的資源消耗。

那麼Java中有提供ThreadPoolExecutor線程池的類實現,Java也對其封裝了Executors的四種靜態使用方法,先來講一下四種線程池的使用。

1.newFixedThreadPool

fixed的意思就是固定, 見名知意就是創建一個固定容量的線程池,用戶傳要創建幾個線程,那麼所有的任務都由這幾個線程來工作。代碼示例如下:

這個代碼模擬了開3個線程處理30個任務,看一下輸出結果

public static void FixedThreadPoolDemo() throws InterruptedException {
        //創建一個定長線程池
        //定長線程池的特點:固定線程總數,空閒線程用於執行任務,如果線程都在使用的話,後續任務處於等待狀態
        //在線程池中的線程執行任務後在執行後續的任務
        //如果任務處於等待的狀態,備選的等待算法默認爲(FIFO(先進先出))也可以設置LIFO(後進先出)
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 1; i <= 30; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("操作的線程:"+Thread.currentThread().getName()+"  索引:"+index);
                    //list.add(Thread.currentThread().getName().split("-")[3]);
                }
            });
        }
        Thread.sleep(1000);
        // OptionalInt max = list.stream().mapToInt(str -> Integer.parseInt(str.toString())).max();
        //shutdown()代表關閉線程池(等待所有線程完成)
        //shutdownNow()代表立即終止線程池的運行,不等待線程,不推薦使用
        executorService.shutdown();
 public static void main(String[] args) throws InterruptedException {
        FixedThreadPool.FixedThreadPoolDemo();
    }

可以看出線程使用一直都是1,2,3個線程,不會在額外開闢線程

點進去看newFixedThreadPool方法,內部使用的ThreadPoolExecutor類創建線程池,講一下參數把。

第1個參數:核心線程數,傳的是3,這裏nThreads就是那個3,

第2個參數:最大線程數也設置的是nThreads,代表我們所開的線程最大的也就是3個,

第3個參數:keepAliveTime最大線程數空閒存活時間,這裏的0L相當於空閒了立即銷燬最大線程數,但是我們的核心線程數也是3,所以不存在銷燬

第4個參數:基於第3個參數,是它的單位

第5個參數:隊列,用於等待線程處理的任務隊列存儲。這裏用的LinkedBlockingQueue,阻塞的無界隊列

2.newCachedThreadPool

還是見名知意可緩存線程池,用戶不傳要開多少個線程,根據任務進行分配線程,線程數量可能會很多,代碼示例如下:

模擬200個任務進行操作,看看會開多少個線程

public static void cacheThreadDemo() throws InterruptedException {
        //創建一個可緩存線程池
        //ExecutorService調度器對象,用於管理線程池
        //Executors.newCachedThreadPool()創建一個可緩存線程池
        //可緩存線程池的特點:無限大,如果線程中沒有可用的則創建,有空閒線程則利用起來
        ExecutorService executorService = Executors.newCachedThreadPool();
        // List list = new ArrayList();
        int[] strs = new int[400];
        for (int i = 1; i <= 200; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("操作的線程:"+Thread.currentThread().getName() + "  索引:" + index);
                    //list.add(Thread.currentThread().getName().split("-")[3]);
                    strs[index] = Integer.parseInt(Thread.currentThread().getName().split("-")[3]);
                }
            });
        }
        Thread.sleep(1000);
        int max = Arrays.stream(strs).max().getAsInt();
        System.out.println("開闢空間最大的線程池名稱是:" + max);
        executorService.shutdown();
    }
public static void main(String[] args) throws InterruptedException {
        CachedThreadPool.cacheThreadDemo();
}

有開闢的線程,有重用的線程,本次示例開了79個。

內部調用ThreadPoolExecutor,核心線程數是0,最大線程無止盡,直到內存用完,最大線程池空閒存活時間60秒,等待的隊列是使用SynchronousQueue,SynchronousQueue是一個內部只能包含一個元素的隊列,插入元素到隊列的線程被阻塞,直到另一個線程從隊列中獲取了隊列中存儲的元素

3.newSingleThreadExecutor

見名知意,是單例,只會創建一個線程處理任務。代碼示例如下:

public static void singleThreadDemo() throws InterruptedException {
        //創建一個單線程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 1; i <= 6; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + index);
                    //list.add(Thread.currentThread().getName().split("-")[3]);
                }
            });
        }
        Thread.sleep(1000);
        executorService.shutdown();
    }
 public static void main(String[] args) throws InterruptedException {
        singlePool.singleThreadDemo();
    }

結果如下所示:只會創造一個線程來處理任務。

實現是核心線程數設置爲1,最大線程池數設置爲1,線程池存活時間是空閒立即銷燬,隊列是阻塞無界隊列

4.newScheduledThreadPool

名字裏有任務調度,一般java是定期執行一些事情,那麼這裏也是,延遲定期執行事務,示例代碼如下:

public static void scheduledPoolDemo() {
        ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
        // 延遲1秒執行,每3秒執行一次
        scheduledPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("操作線程:" + Thread.currentThread().getName() + "   " + new Date() + "延遲1秒執行,每3秒執行一次");
            }
        }, 1, 3, TimeUnit.SECONDS);
    }
public static void main(String[] args) {
    scheduledPoolDemo();
}

執行結果如下:

咱們的核心線程數是5個,這裏最大線程數是無數個,0秒最大線程空閒,隊列採用延遲隊列方式。

ThreadPoolExecutor講解

那麼這4大線程池介紹完畢了,在介紹下ThreadPoolExecutor,阿里巴巴規範說創建線程池最好用ThreadPoolExecutor,而不要用上述說的4個,原因是自己實現的可以設置隊列爲不是無界隊列,也能控制核心線程數和最大核心線程數,根據自己的業務場景選擇合適的參數。下面來講講7大參數,

1.corePoolSize:核心線程數,當有任務進來時還未達到核心線程數,則創建核心線程

    1.1 當線程數沒有達到核心線程數最大值的時候,新任務會繼續創建線程,不會複用線程池的線程

    1.2 核心線程一般不會銷燬,空閒也不會銷燬,除非通過方法allowCoreThreadTimeOut(boolean value)設置爲true時,超時也            同樣會被銷燬

    1.3 生產環境首次初始化的時候,可以調用prestartCoreThread(),prestartAllCoreThread()來預先創建所有的和核心線程,避免            第一次調用緩慢。

2.maximumPoolSize:最大線程數,線程池中能夠容納(包含核心線程數)同時執行的最大線程數,此知大於等於1

3.keepAliveTime:最大線程池中線程數量超過核心線程數時,多餘的空閒線程存活的時間,當空閒時間達到keepAliveTime時,多餘線程就會被(最大線程池的線程)銷燬,當keepAliveTime設置爲0時,表明最大線程池空閒立即銷燬。

4.unit:keepAliveTime的單位

5.workQueue:等待執行的任務隊列,核心線程滿了,新的任務就會在加入等待隊列中。

6.threadFactory:表示生成線程池中工作線程的線程工廠,用於創建線程,一般默認即可。

7.handler:拒絕策略,沒有空閒的線程處理任務,並且等待隊列已滿,再有新任務進來如何來拒絕請求的runnable的策略。

 拒絕策略還蠻重要的,記住這四個不同的拒絕策略:

  7.1 AbortPolicy:直接拋出RejectedExecutionException異常阻止系統正常運行,默認使用的異常。

  7.2 DiscardPolicy:默默丟棄無法處理的任務,不予任何處理也不拋出異常。

  7.3 DiscardOldestPolicy:拋棄隊列中等待最久的任務,然後把當前任務加入隊列中嘗試再次提交當前任務。

  7.4 CallerRunsPolicy:"調用者運行"一種調節機制,該策略既不會拋棄任務,也不會拋出異常,二是將某些任務回退到調用者,從而降低新任務流量。

自定義的線程池代碼示例demo

public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
                5,
                2L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3), //不寫的話默認也是Integer.MAX_VALUE
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());//默認的拒絕策略

        try {
            for (int i = 0; i <8; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t" + "辦理業務");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown(); //關閉線程池
        }
    }

結果:

線程池執行流程

1.提交任務時,檢查覈心線程池是否已滿,沒有滿則創建線程,核心線程池滿了走2

2.檢查隊列是否已滿,沒有則把任務放入隊列中,隊列滿了走3

3.檢查最大線程池數是否已滿,沒有則繼續創建線程執行任務,滿了走4

4.執行拒絕策略

以上就是線程池的全部內容,希望可以幫到你!下節抽空會講下隊列哦!

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