1. 程式人生 > >多線程學習:線程池

多線程學習:線程池

sin 順序 集合 場景 9.png 是否 exceptio ava cached

什麽是線程池?線程池的好處?

  線程池的概念:線程池就首先創建一些線程,它們的集合稱為線程池。使用線程池可以很好地提高性能,線程池在系統啟動時即創建大量空閑的線程,程序將一個任務傳給線程池,線程池就會啟動一條線程來執行這個任務,執行結束以後,該線程並不會死亡,而是再次返回線程池中成為空閑狀態,等待執行下一個任務。

  線程池的好處:1、降低資源消耗,通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。2、提高響應速度,當任務到達時,任務可以不需要等到線程創建就能立即執行。3、提高線程的可管理性,線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,

Java中的ThreadPoolExecutor類

  java在JDK1.5後加入了java.util.concurrent包,Executor是java.util.concurrent一個接口。Executor最頂層的實現是ThreadPoolExecutor類。Executor工廠類提供newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool方法其實也只是ThreadPoolExecutor的構造函數參數不同而已。通過傳入不同的參數,就可以構造出適用於不同應用場景下的線程池。下面對ThreadPoolExecutor構造中的的幾個參數講解一下。

corePoolSize:

核心池的大小。 當有任務來之後,就會創建一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中。
maximumPoolSize: 線程池最大線程數,它表示在線程池中最多能創建多少個線程。
keepAliveTime: 表示線程沒有任務執行時最多保持多久時間會終止。
unit:參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性。

TimeUnit.DAYS;               //
TimeUnit.HOURS;             //小時
TimeUnit.MINUTES;           //
分鐘 TimeUnit.SECONDS; // TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //納秒

workQueue:一個阻塞隊列,用來存儲等待執行的任務。

threadFactory:線程工廠,主要用來創建線程。

handler:表示當拒絕處理任務時的策略,有以下四種取值:

ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。 
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。 
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重復此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務 

線程池的幾種創建方式:

1、newCachedThreadPool:創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。

public static void main(String[] args) {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newCachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ",i:" + temp);

                }
            });
        }
    }

技術分享圖片

結論:線程池為無限大,當執行第二個任務時第一個任務已經完成,會復用執行第一個任務的線程,而不用每次新建線程。

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

public static void main(String[] args) {
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newFixedThreadPool.execute(new Runnable() {

                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ",i:" + temp);

                }
            });
        }
    }

技術分享圖片

結論:因為線程池大小為3,每個任務輸出打印結果後sleep 2秒,所以每兩秒打印3個結果。
定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()(返回可用處理器的Java虛擬機的數量。)

3、newScheduledThreadPool:創建一個定長線程池,即使是空線程也會保留。支持定時及周期性任務執行。

public static void main(String[] args) {
        ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newScheduledThreadPool.schedule(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName()+",i:" + temp);
                }
            }, 3, TimeUnit.SECONDS);
        }
    }

技術分享圖片

結論:表示延遲三秒執行。

4、newSingleThreadExecutor:創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            newSingleThreadExecutor.execute(new Runnable() {

                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+",index:" + index);
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                }
            });
        }
    }

技術分享圖片

結論:相當於順序執行各個任務

線程池的原理:

技術分享圖片

如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務;如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閑線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務;如果隊列已經滿了,則在總線程數不大於maximumPoolSize的前提下,則創建新的線程如果當前線程池中的線程數目達到maximumPoolSize,則會采取任務拒絕策略進行處理;如果線程池中的線程數量大於 corePoolSize時,如果某線程空閑時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許為核心池中的線程設置存活時間,那麽核心池中的線程空閑時間超過keepAliveTime,線程也會被終止。

自定義線程池:

    public static void main(String[] args) {
        // 核心線程數1,最大線程2,隊列長度為3
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
        // 任務一 創建線程 執行
        executor.execute(new TaskThred("任務1"));
        // 任務二緩存到隊列中 復用同一個線程
        executor.execute(new TaskThred("任務2"));
        // 任務三緩存到隊列中 復用同一個線程
        executor.execute(new TaskThred("任務3"));
        // 任務四緩存到隊列中 復用同一個線程
        executor.execute(new TaskThred("任務4"));
        // 平滑的關閉線程池。(如果還有未執行完的任務,就等待它們執行完)。
        executor.shutdown();
    }
}

class TaskThred implements Runnable {
    private String taskName;

    public TaskThred(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + taskName);
    }

技術分享圖片

結論:可以看到第一個線程直接創建執行,後面的線程因為大於核心線程數,就放進緩存隊列裏面等待取出執行

    public static void main(String[] args) {
        // 核心線程數1,最大線程2,隊列長度為3
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
        // 任務一 創建線程 執行
        executor.execute(new TaskThred("任務1"));
        // 任務二緩存到隊列中 復用同一個個線程
        executor.execute(new TaskThred("任務2"));
        // 任務三緩存到隊列中 復用同一個個線程
        executor.execute(new TaskThred("任務3"));
        // 任務四緩存到隊列中 復用同一個個線程
        executor.execute(new TaskThred("任務4"));
        //任務5   超過隊列,判斷是否大於最大線程數。
        executor.execute(new TaskThred("任務5"));
        // 平滑的關閉線程池。(如果還有未執行完的任務,就等待它們執行完)。
        executor.shutdown();
    }

技術分享圖片

結論:可以看到任務5超過隊列之後但是沒有超過最大線程數。就從新創建線程執行。

public static void main(String[] args) {
        // 核心線程數1,最大線程2,隊列長度為3
        ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
        // 任務一 創建線程 執行
        executor.execute(new TaskThred("任務1"));
        // 任務二緩存到隊列中 復用同一個個線程
        executor.execute(new TaskThred("任務2"));
        // 任務三緩存到隊列中 復用同一個個線程
        executor.execute(new TaskThred("任務3"));
        // 任務四緩存到隊列中 復用同一個個線程
        executor.execute(new TaskThred("任務4"));
        //任務5   超過隊列,判斷是否大於最大線程數。
        executor.execute(new TaskThred("任務5"));
        //任務6超過隊列,也超過最大核心線程數,會報錯,出現拒絕策略
        executor.execute(new TaskThred("任務6"));
        // 平滑的關閉線程池。(如果還有未執行完的任務,就等待它們執行完)。
        executor.shutdown();
    }

技術分享圖片

結論:當超過隊列緩存和最大線程數的時候就會采取拒絕策略

多線程學習:線程池