1. 程式人生 > >JAVA四種執行緒池詳解

JAVA四種執行緒池詳解

執行緒池採取上述的流程進行設計是為了減少獲取全域性鎖的次數線上程池完成預熱(當前執行的執行緒數大於或等於corePoolSize)之後,幾乎所有的excute方法呼叫都執行步驟2。

執行緒池的作用:

1. 執行緒池作用就是限制系統中執行執行緒的數量。
2. 根據系統的環境情況,可以自動或手動設定執行緒數量,達到執行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用執行緒池控制執行緒數量,其他執行緒排隊等候。一個任務執行完畢,再從佇列的中取最前面的任務開始執行。若佇列中沒有等待程序,執行緒池的這一資源處於等待。當一個新任務需要執行時,如果執行緒池中有等待的工作執行緒,就可以開始運行了;否則進入等待佇列。

為什麼要用執行緒池:

1.減少了建立和銷燬執行緒的次數,每個工作執行緒都可以被重複利用,可執行多個任務。

2.可以根據系統的承受能力,調整執行緒池中工作線執行緒的數目,防止因為消耗過多的記憶體,而把伺服器累趴下(每個執行緒需要大約1MB記憶體,執行緒開的越多,消耗的記憶體也就越大,最後宕機)。

Java裡面執行緒池的頂級介面是Executor,但是嚴格意義上講Executor並不是一個執行緒池,而只是一個執行執行緒的工具。真正的執行緒池介面是ExecutorService

Java類庫中提供的執行緒池


Java的四種執行緒池

在這裡,我們詳細講解前四種執行緒池。Java通過Executors提供四種執行緒池,分別為:

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

ThreadPoolExecutor

首先,我們熟悉一下最底層的ThreadPoolExecutor:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
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;
    }
構造方法引數講解
引數名 作用
corePoolSize 核心執行緒池大小
maximumPoolSize 最大執行緒池大小
keepAliveTime 執行緒池中超過corePoolSize數目的空閒執行緒最大存活時間;可以allowCoreThreadTimeOut(true)使得核心執行緒到這個有效時間也將關閉
TimeUnit keepAliveTime時間單位
workQueue 阻塞任務佇列
threadFactory 新建執行緒工廠
RejectedExecutionHandler 當提交任務數超過maxmumPoolSize+workQueue之和時,任務會交給RejectedExecutionHandler來處理

需要注意的點:

1.當執行緒池小於corePoolSize時,新提交任務將建立一個新執行緒執行任務,即使此時執行緒池中存在空閒執行緒。
2.當執行緒池達到corePoolSize時,新提交任務將被放入workQueue中,等待執行緒池中任務排程執行
3.當workQueue已滿,且maximumPoolSize>corePoolSize時,新提交任務會建立新執行緒執行任務
4.當提交任務數超過maximumPoolSize時,新提交任務由RejectedExecutionHandler處理
5.當執行緒池中超過corePoolSize數目的執行緒,空閒時間達到keepAliveTime時,關閉空閒執行緒
6.當設定allowCoreThreadTimeOut(true)時,執行緒池中corePoolSize執行緒空閒時間達到keepAliveTime也將關閉


newCachedThreadPool(可快取執行緒池)

定義:

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

構造一個緩衝功能的執行緒池,配置corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,keepAliveTime=60s,以及一個無容量的阻塞佇列 SynchronousQueue,因此任務提交之後,將會建立新的執行緒執行;執行緒空閒超過60s將會銷燬

建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。示例程式碼如下:

        ExecutorService es1 = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            try {
                Thread.sleep(index*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            es1.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+": "+ index);
                }
            });
        }

輸出:
pool-1-thread-1: 0
pool-1-thread-1: 1
pool-1-thread-1: 2
pool-1-thread-1: 3
pool-1-thread-1: 4
pool-1-thread-1: 5
pool-1-thread-1: 6
pool-1-thread-1: 7
pool-1-thread-1: 8
pool-1-thread-1: 9
可以看出,當下一個執行緒開始時,前一個執行緒使用完後延時了index秒,則前一個執行緒處於空閒狀態,後面的執行緒依次拿這個執行緒(pool-1-thread-1)使用。

newFixedThreadPool (定長執行緒池,佇列)

定義:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
構造一個固定執行緒數目的執行緒池,配置的corePoolSize與maximumPoolSize大小相同,同時使用了一個無界
LinkedBlockingQueue存放阻塞任務,因此多餘的任務將存在再阻塞佇列,不會由RejectedExecutionHandler處理

示例程式碼如下:

        ExecutorService es2 = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            es2.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+": "+ index);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
輸出:
pool-1-thread-1: 0
pool-1-thread-2: 1
pool-1-thread-3: 2
pool-1-thread-2: 3
pool-1-thread-1: 4
pool-1-thread-3: 5
pool-1-thread-2: 6
pool-1-thread-1: 7
pool-1-thread-3: 8
pool-1-thread-2: 9
每隔兩秒同時列印三個執行緒。
定長執行緒池的大小最好根據系統資源進行設定。如Runtime.getRuntime().availableProcessors()。可參考PreloadDataCache。

newScheduledThreadPool

定義:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

構造有定時功能的執行緒池,配置corePoolSize,無界延遲阻塞佇列DelayedWorkQueue;有意思的是:maximumPoolSize=Integer.MAX_VALUE,由於DelayedWorkQueue是無界佇列,所以這個值是沒有意義的。

支援定時及週期性任務執行。這裡舉兩個例子(延遲+定時):

        ScheduledExecutorService es3 = Executors.newScheduledThreadPool(5);
        //延時3s再執行執行緒
        es3.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("delay 3 seconds");
            }
        }, 3, TimeUnit.SECONDS);
        //先延遲3秒,然後每隔3s執行執行緒一次
        es3.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("delay 3 seconds, and excute every 3 seconds");
            }
        }, 3, 3, TimeUnit.SECONDS);

輸出:
delay 3 seconds
delay 3 seconds, and excute every 3 seconds
delay 3 seconds, and excute every 3 seconds
delay 3 seconds, and excute every 3 seconds
delay 3 seconds, and excute every 3 seconds
.....................

延遲和定時任務互不影響,ScheduledExecutorService比Timer更安全,功能更強大

newSingleThreadExecutor

構造一個只支援一個執行緒的執行緒池,它只會用唯一的工作執行緒來執行任,配置corePoolSize=maximumPoolSize=1,無界阻塞佇列LinkedBlockingQueue;保證任務由一個執行緒按照指定順序(FIFO, LIFO, 優先順序)序列執行。

示例程式碼如下:

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

                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName()+": "+ index);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            });
        }
輸出:
pool-1-thread-1: 0
pool-1-thread-1: 1
pool-1-thread-1: 2
pool-1-thread-1: 3
pool-1-thread-1: 4
pool-1-thread-1: 5
pool-1-thread-1: 6
pool-1-thread-1: 7
pool-1-thread-1: 8
pool-1-thread-1: 9

同一個執行緒依順序執行