1. 程式人生 > >並發編程(二):線程池

並發編程(二):線程池

imu 並發數 提交 任務調度 core info 回收 log 減少

相比於線程池,我們可能接觸new Thread更多一點,既然有了new Thread我們為什麽還要使用線程池呢?

new Thread的弊端

a、每次new Thread新建對象,性能差

b、線程缺乏統一管理,可能無限制的新建線程,相互競爭,有可能占用過多系統資源導致死機或者OOM(OutOfMemory)

c、缺少更多功能,如更多執行、定期執行、線程中斷

線程池的優勢

a、重用存在的線程,減少對象創建、消亡的開銷,性能好

b、可有效控制最大並發線程數,提高系統資源利用率,同時可以避免過多資源競爭,避免阻塞

c、提供定時執行、定期執行、單線程、並發數控制等功能

線程池類圖

技術分享圖片

由類圖可以看出,ThreadPoolExecutor類是線程池中最核心的一個類。我們來做重點分析

ThreadPoolExecutor構造參數

a、corePoolSize:核心線程數量,默認情況下(可預創建線程)線程池後線程池中的線程數為0,當有任務後當有任務後就會創建一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列中

b、maximumPoolSize:線程最大線程數

c、workQueue:阻塞隊列,存儲等待執行的任務,有三種取值,ArrayBlockQueue(基於數組的先進先出隊列,創建時必須指定大小)、LinkedBlockingQueue(基於鏈表的先進先出隊列,如果沒有指定此隊列大小,默認為Integer.MAX_VALUE)、SynchronousQueue(不會保存提交的任務,直接新建一個線程來執行新的任務)

d、keepAliveTime:線程沒有任務執行時最多保持多久時間終止,默認情況只有當線程池中的線程數大於corePoolSize時,keepAliveTIme才會起作用,當線程池中的線程數大於corePoolSize,如果一個線程的空閑時間達到keepAliveTime,則會終止。如果調用allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數不大於corePoolSize時keepAliveTime參數也會起作用,直到線程池的線程數為0

e、unit:keepAliveTime的時間單位,有7中取值,如:TimeUnit.DAYS; 天,可具體到納秒

f、threadFactory:線程工廠,用來創建線程

g、rejectHandler:當拒絕處理任務時的策略,通常有四種取值,ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常;ThreadPoolExecutor.DiscardPolicy:丟棄任務,但不拋出異常;ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,重新嘗試執行任務(重復此過程);ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

參數之間的關系如下:

a、如果當前poolsize小於corePoolSize,創建新線程執行任務

b、如果當前poolsize大於corePoolsize,且等待隊列未滿,進入等待隊列

c、如果當前poolsize大於corePoolsize且小於maximumPoolSize,且等待隊列已滿,創建新線程執行任務

d、如果當前poolsize大於corePoolSize且大於maximumPoolSize,且等待隊列已滿則用拒絕策略來處理該任務

e、線程池中的線程執行完任務後不會立刻退出,而是去檢查等待隊列是否有新的線程去執行,如果在keepAliveTime裏等不到新任務,線程就會退出

ThreadPoolExecutor狀態

技術分享圖片

圖中需要註意的是shutdown()和shutdownNow()方法的區別,執行前者後,還在執行的線程會執行完再關閉,執行後者後,線程池會立刻關閉,正在執行的線程不再執行

ThreadPoolExecutor方法

a、execute():提交任務,交給線程池執行

b、submit():提交任務,能夠返回執行結果 execute+Future

c、shutdown():關閉線程池,等待任務都執行完

d、shutdownNow():關閉線程池,不等待任務執行完

e、getTaskCount():線程池已執行的和未執行的任務總數

f、getCompletedTaskCount():已完成的任務數量

g、getPoolSize():線程池當前線程數量

h、getActiveCount():當前線程池正在執行任務的線程數量

Executors類

由上面的類圖可以看出,Executors類為Executor,ExecutorService,ScheduledExecutorService提供了一些工具方法,並且Executors封裝了ScheduledThreadPoolExecutor類和ThreadPoolExecutor類,所以我們倡導在實際使用中使用此類,Executors類提供了四個靜態方法:

a、newCachedThreadPool:創建可緩存線程池,靈活回收空閑線程,如果沒有可回收線程,則創建新線程

技術分享圖片

由上圖可看出,newCachedThreadPool將corePoolSize設置為0,將maximumPoolSize設置為Integer.MAX_VALUE,使用SynchronousQueue,就是說有新任務就創新線程運行,線程空閑超過60秒,則銷毀線程。demo代碼如下:

 public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("task:{}", index);
                }
            });
        }
        executorService.shutdown();
    }

b、newFixedThreadPool:創建定長線程池,可控制線程最大並發數,超出的線程會在隊列中等待

技術分享圖片

newFixedThreadPool創建的線程池corePoolSize和maximumPoolSize值相等,線程空閑後直接銷毀,使用的是LinkedBlockingQueue。demo代碼如下:

public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("task:{}", index);
                }
            });
        }
        executorService.shutdown();
    }

c、newScheduledThreadPool:創建大小無限制線程池(最大為Integer.MAX_VALUE),支持定時及周期性任務執行

技術分享圖片

技術分享圖片

newScheduledThreadPool的特點是可以進行任務調度,最常用的方法是ScheduleAtFixedRate(基於固定時間間隔進行任務調度)和ScheduleWithFixedDelay(基於不固定時間間隔進行任務調度,主要取決於任務執行時間長短), demo代碼如下:

 public static void main(String[] args) {

        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        executorService.schedule(new Runnable() {
            @Override
            public void run() {
                log.warn("schedule run");
            }
        },3,TimeUnit.SECONDS);

        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                log.warn("schedule run");
            }
        },1,3,TimeUnit.SECONDS);
        
    }

  

d、newSingleThreadExecutor:創建一個單線程化的線程池,只會用唯一的線程來執行任務

技術分享圖片

newSingleThreadExecutor將corePoolSize和maximumPoolSize都固定為1,線程空閑時直接銷毀,使用的LinkedBlockingQueue.demo代碼如下:

 public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("task:{}", index);
                }
            });
        }
        executorService.shutdown();
    }

線程池合理配置

線程池的具體配合需要按照實際情況進行調整,以下為兩條參考原則,可先按此原則設置再根據系統負載,資源利用情況進行調整。

a、cpu密集型任務,需要盡量壓榨cpu,參考值可以設置為ncpu+1;

b、io密集型任務,參考值可以設置為2*ncpu

並發編程(二):線程池