Java-線程池

這裏寫圖片描述

1.new Thread的弊端

執行一個異步任務你還只是如下new Thread嗎?

new Thread(new Runnable() {

    @Override
    public void run() {
         // TODO Auto-generated method stub
    }
}).start();

那你就out太多了,new Thread的弊端如下:

  1. 每次new Thread新建對象性能差。
  2. 線程缺乏統一管理,可能無限制新建線程,相互之間競爭,及可能佔用過多系統資源導致死機或oom。
  3. 缺乏更多功能,如定時執行、定期執行、線程中斷。

相比new Thread,Java提供的四種線程池的好處在於:

  1. 重用存在的線程,減少對象創建、消亡的開銷,性能佳。
  2. 可有效控制最大併發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。
  3. 提供定時執行、定期執行、單線程、併發數控制等功能。

2.Java 自帶線程池

Java通過Executors提供四種線程池,分別爲:

  1. newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
  2. newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
  3. newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
  4. newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

主要方法:

1.void execute(Runnable command):
在未來某個時間執行給定命令。該命令可能在新的線程、已入池的線程或者正調用的線程中執行,這由Executor決定。

2.void shutdown():
啓動一次順序關閉,執行以前提交的任務,但不接受新任務。如果已經關閉,則調用沒有其它作用。

2.1.newCachedThreadPool

創建一個可緩存的線程池。必要的時候創建新線程來處理請求,也會重用線程池中已經處於可用狀態的線程。如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閒(60秒不執行任務)的線程;當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。

此類型線程池特別適合於耗時短,不需要考慮同步的場合。

測試代碼:

    /**
     * 可緩存線程池
     */
    private static void testCachedThreadPool() {
        System.out.println("這是可緩存線程池:");
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(index * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            cachedThreadPool.execute(new Runnable() {

                @Override
                public void run() {
                    print(index + "");
                }
            });
        }
    }

輸出結果:

這是可緩存線程池:
1464230153368: pool-1-thread-1: 0
1464230154372: pool-1-thread-1: 1
1464230156376: pool-1-thread-1: 2
1464230159380: pool-1-thread-1: 3
1464230163384: pool-1-thread-1: 4
1464230168386: pool-1-thread-1: 5
1464230174391: pool-1-thread-1: 6
1464230181395: pool-1-thread-1: 7
1464230189397: pool-1-thread-1: 8
1464230198400: pool-1-thread-1: 9

2.2.newFixedThreadPool

創建固定大小的線程池,以無界隊列方式運行。線程池滿且線程都爲活動狀態的時候如果有新任務提交進來,它們會等待直到有線程可用。線程池的大小一旦達到最大值就會保持不變,如果某個線程因爲執行異常而結束,那麼線程池會補充一個新線程。顯式調用shutdown將關閉線程池。

此類型線程池比較符合常用場合。

測試代碼:

    /**
     * 定長線程池
     */
    private static void testFixedThreadPool() {
        System.out.println("這是定長線程池:");
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            fixedThreadPool.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        print(index + "");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
        }
    }

輸出結果:

這是定長線程池:
1464229952132: pool-1-thread-1: 0
1464229952133: pool-1-thread-2: 1
1464229952133: pool-1-thread-3: 2
1464229953136: pool-1-thread-3: 3
1464229953136: pool-1-thread-1: 4
1464229953136: pool-1-thread-2: 5
1464229954138: pool-1-thread-1: 6
1464229954138: pool-1-thread-2: 8
1464229954138: pool-1-thread-3: 7
1464229955139: pool-1-thread-1: 9

2.3.newScheduledThreadPool

創建可定時運行(初始延時),運行頻率(每隔多長時間運行,還是運行成功一次之後再隔多長時間再運行)的線程池。

此類型線程池適合定時以及週期性執行任務的場合

特別注意:

  1. 初始延遲2秒後,每間隔3秒運行一次線程:
    schedulePool.scheduleAtFixedRate(new MyThread(), 2, 3, TimeUnit.SECONDS);
  2. 初始延遲2秒後,每運行成功後再等3秒運行一次線程:
    schedulePool.scheduleWithFixedDelay(new MyThread(), 2, 3, TimeUnit.SECONDS);

測試代碼:

    /**
     * 週期線程池
     */
    private static void testScheduledThreadPool() {
        System.out.println("這是週期線程池:");
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        scheduledThreadPool.scheduleAtFixedRate(new Runnable() {

            @Override
            public void run() {
                print("delay 3 seconds");
            }
        }, 2, 3, TimeUnit.SECONDS);
    }

輸出結果:

這是週期線程池:
1464231196776: pool-1-thread-1: delay 3 seconds
1464231199776: pool-1-thread-1: delay 3 seconds
1464231202775: pool-1-thread-2: delay 3 seconds
1464231205777: pool-1-thread-1: delay 3 seconds
1464231208775: pool-1-thread-3: delay 3 seconds
1464231211775: pool-1-thread-3: delay 3 seconds
1464231214776: pool-1-thread-3: delay 3 seconds
1464231217777: pool-1-thread-3: delay 3 seconds
1464231220776: pool-1-thread-3: delay 3 seconds

2.4.newSingleThreadExecutor

創建一個單線程的線程池,以無界隊列方式運行。這個線程池只有一個線程在工作(如果這個唯一的線程因爲異常結束,那麼會有一個新的線程來替代它。)此線程池能夠保證所有任務的執行順序按照任務的提交順序執行,同一時段只有一個任務在運行。

此類型線程池特別適合於需要保證執行順序的場合。
測試代碼:

    /**
     * 單線程池
     */
    private static void testSingleThreadExecutor() {
        System.out.println("這是單線程池:");
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        print(index + "");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
        }
    }

輸出結果:

這是單線程池:
1464230560509: pool-1-thread-1: 0
1464230561514: pool-1-thread-1: 1
1464230562519: pool-1-thread-1: 2
1464230563524: pool-1-thread-1: 3
1464230564529: pool-1-thread-1: 4
1464230565534: pool-1-thread-1: 5
1464230566536: pool-1-thread-1: 6
1464230567541: pool-1-thread-1: 7
1464230568546: pool-1-thread-1: 8
1464230569551: pool-1-thread-1: 9

3.自定義線程池

3.1. 四種自帶線程池的實際構造方法:

1.CachedThreadPool:

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

2.FixedThreadPool:

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

3.ScheduledThreadPool:

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

跳轉至:

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

4.SingleThreadExecutor:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

3.2.ThreadPoolExecutor構造方法:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

參數解釋:

  1. corePoolSize(線程池的基本大小):
    當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於線程池基本大小時就不再創建。如果調用了線程池的prestartAllCoreThreads方法,線程池會提前創建並啓動所有基本線程。
  2. maximumPoolSize(線程池最大大小):
    線程池允許創建的最大線程數。如果隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得注意的是如果使用了無界的任務隊列這個參數就沒什麼效果。
  3. keepAliveTime(線程活動保持時間):
    線程池的工作線程空閒後,保持存活的時間。所以如果任務很多,並且每個任務執行的時間比較短,可以調大這個時間,提高線程的利用率。
  4. unit(線程活動保持時間的單位):
    可選的單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
  5. workQueue(任務隊列):
    用於保存等待執行的任務的阻塞隊列。 可以選擇以下幾個阻塞隊列。
    1. ArrayBlockingQueue:
      是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。
    2. LinkedBlockingQueue:
      一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
    3. SynchronousQueue:
      一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。
    4. PriorityBlockingQueue:
      一個具有優先級的無限阻塞隊列。
  6. threadFactory(線程工廠):
    用於設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程設置更有意義的名字。
  7. handler(飽和策略):
    當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常。以下是JDK1.5提供的四種策略。
    1. AbortPolicy:直接拋出異常。
    2. CallerRunsPolicy:只用調用者所在線程來運行任務。
    3. DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
    4. DiscardPolicy:不處理,丟棄掉。當然也可以根據應用場景需要來實現RejectedExecutionHandler接口自定義策略。如記錄日誌或持久化不能處理的任務。

3.3.簡單的自定義線程池

核心有兩個線程,最大線程數量可無限,存活時間60s

測試代碼:

    /**
     * 自定義線程池
     */
    private static void testCustomThreadPool() {
        System.out.println("這是自定義線程池:");
        ExecutorService customThreadExecutor = new ThreadPoolExecutor(
                2, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>());
        for (int i = 0; i < 10; i++) {
            final int index = i;
            customThreadExecutor.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        print(index + "");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
        }
    }

輸出結果:

這是自定義線程池:
1464235380782: pool-1-thread-1: 0
1464235380783: pool-1-thread-2: 1
1464235381787: pool-1-thread-2: 2
1464235381787: pool-1-thread-1: 3
1464235382790: pool-1-thread-2: 5
1464235382790: pool-1-thread-1: 4
1464235383793: pool-1-thread-1: 7
1464235383793: pool-1-thread-2: 6
1464235384798: pool-1-thread-1: 8
1464235384798: pool-1-thread-2: 9


參考目錄:
1. Java:多線程,線程池,用Executors靜態工廠生成常用線程池
2. Java(Android)線程池

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