淺談java線程池

線程池有啥好處

  • 降低資源消耗:通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
  • 提高響應(yīng)速度:當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。
  • 提高線程的可管理性:可以有效的控制最大并發(fā)線程數(shù),提高系統(tǒng)資源的使用率,同時(shí)避免過多資源競爭,避免堵塞。提供定時(shí)執(zhí)行,定期執(zhí)行,單線程,并發(fā)控制等功能。

介紹一下ThreadPoolExecutor

構(gòu)造器與參數(shù)

在java源碼中它有四個(gè)構(gòu)造器,分別如下:

 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) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
 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;
    }

最終都是調(diào)用最下面那個(gè)構(gòu)造方法,只是參數(shù)不同罷了。那現(xiàn)在來說說這些參數(shù)都是什么意思。

  • corePoolSize:線程池中所保存的核心線程數(shù),包括空閑線程。
  • maximumPoolSize:線程池允許創(chuàng)建的最大線程數(shù)。
  • keepAliveTime:線程池的工作線程空閑時(shí)間。線程池的工作線程空閑后,保持存活的時(shí)間。所以如果任務(wù)很多,并且每個(gè)任務(wù)執(zhí)行的時(shí)間比較短,可以調(diào)大這個(gè)時(shí)間,提高線程的利用率。
  • TimeUnit:線程活動(dòng)保持時(shí)間的單位。可選的單位有天,小時(shí),分鐘等。
  • BlockingQueue:任務(wù)執(zhí)行前保存任務(wù)的隊(duì)列,僅保存由execute方法提交的Runnale任務(wù)。
    隊(duì)列的種類:
  1. ArrayBlockingQueue: 是一個(gè)基于數(shù)組結(jié)構(gòu)的有界阻塞隊(duì)列,此隊(duì)列按FIFO(先進(jìn)先出)原則對(duì)元素進(jìn)行排序。
  2. LinkedBlockingQueue:一個(gè)基于鏈表結(jié)構(gòu)的阻塞隊(duì)列,此隊(duì)列按FIFO原則對(duì)元素進(jìn)行排序,吞吐量通常高于ArrayBlockingQueue。靜態(tài)工廠方法Executors.newFixedThreadPool()使用了這個(gè)隊(duì)列。
  3. SynchronousQueue:一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列。每插入操作必須等到另一下線程調(diào)用移除操作,否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于LinkedBlockingQueue,靜態(tài)工廠方法Executors.newCachedThreadPool使用了這個(gè)隊(duì)列。
  4. PriorityBlockingQueue:一個(gè)具有優(yōu)先級(jí)的無限阻塞隊(duì)列。

它與corePoolSize和maximumPoolSize的關(guān)系如下:

線程池處理流程.jpg
  • ThreadFactory :用于設(shè)置創(chuàng)建線程的工廠,可以通過線程工廠給每個(gè)創(chuàng)建出來的線程設(shè)置更有意義的名字和優(yōu)先級(jí)等。
  • RejectedExecutionHandler :飽和策略。當(dāng)線程池處于飽和狀態(tài),那么必須采取一種策略處理提交的新任務(wù)。這個(gè)策略默認(rèn)情況下是AbortPolicy,表示無法處理新任務(wù)時(shí)拋出異常。以下是JDK1.5提供的四種策略。
  1. AbortPolicy:直接拋出異常。
  2. CallerRunsPolicy:只用調(diào)用者所在線程來運(yùn)行任務(wù)
  3. DiscardOldestPolicy 丟棄隊(duì)列里最近的一個(gè)任務(wù),并執(zhí)行當(dāng)前任務(wù)。
  4. DiscardPolicy 不處理,丟棄掉
    當(dāng)然也可以根據(jù)應(yīng)用場景需要來實(shí)現(xiàn)RejectedExecutionHandler接口自定義策略,如記錄日志或持久化不能處理的任務(wù)。

2 執(zhí)行方法

  • execute方法
    我們可以使用execute提交任務(wù),但是execute沒有返回值,所以無法判斷任務(wù)是否被線程池執(zhí)行成功。
threadsPool.execute(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
            }
        });
  • submit 方法
    我們也可以使用submit方法來提交任務(wù),它會(huì)返回一個(gè)future,那么我們可以通過這個(gè)future來判斷任務(wù)是否執(zhí)行成功,通過future的get方法來獲取返回值,get方法會(huì)阻塞住直到任務(wù)完成,而使用get(long timeout, TimeUnit unit)方法則會(huì)阻塞一段時(shí)間后立即返回,這時(shí)有可能任務(wù)沒有執(zhí)行完。
Future<Object> future = executor.submit(task);
try {
     Object s = future.get();
} catch (InterruptedException e) {
    // 處理中斷異常
} catch (ExecutionException e) {
    // 處理無法執(zhí)行任務(wù)異常
} finally {
    // 關(guān)閉線程池
    executor.shutdown();
}
  • shutdown與shutdownNow
    通過調(diào)用shutdown或shutdownNow方法來關(guān)閉線程,它們的原理是遍歷線程池中的工作線程,然后逐個(gè)調(diào)用線程的interrupt方法來中斷線程,所以無法響應(yīng)中斷的任務(wù)可能永遠(yuǎn)無法終止。但它們存在一定的區(qū)別,shutdownNow首先將線程池的狀態(tài)設(shè)置成stop,,然后嘗試停止所有的正在執(zhí)行或暫停任務(wù)的線程,并返回等待執(zhí)行任務(wù)的列表,而shutdown只是將線程池的狀態(tài)設(shè)置成SHUTDOWN狀態(tài),然后中斷所有沒有正在執(zhí)行任務(wù)的線程。

Executors與ThreadPoolExecutor

Executors提供四種線程池

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

創(chuàng)建一個(gè)可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。線程池為無限大,當(dāng)執(zhí)行第二個(gè)任務(wù)時(shí)第一個(gè)任務(wù)已經(jīng)完成,會(huì)復(fù)用執(zhí)行第一個(gè)任務(wù)的線程,而不用每次新建線程。

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

創(chuàng)建一個(gè)定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待。定長線程池的大小最好根據(jù)系統(tǒng)資源進(jìn)行設(shè)置,如Runtime.getRuntime().availableProcessors()。

  • **newScheduledThreadPool **
  public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

ScheduledThreadPoolExecutor是ThreadPoolExecutor的子類。


  public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);
    }

其中的隊(duì)列是DelayedWorkQueue,支持定時(shí)及周期性任務(wù)執(zhí)行.
比如說:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);  
  // 表示延遲3秒執(zhí)行
  scheduledThreadPool.schedule(new Runnable() {  
   public void run() {  
    System.out.println("delay 3 seconds");  
   }  
  }, 3, TimeUnit.SECONDS);  
 }  
// 表示延遲1秒后每3秒執(zhí)行一次
 scheduledThreadPool.scheduleAtFixedRate(new Runnable() {  
   public void run() {  
    System.out.println("delay 1 seconds, and excute every 3 seconds");  
   }  
  }, 1, 3, TimeUnit.SECONDS);  
 } 
  • **newSingleThreadExecutor **
 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來執(zhí)行任務(wù),保證所有的任務(wù)按指定順序執(zhí)行。
查看Executors源碼我們知道,Executors類提供了使用ThreadPoolExecutor 的簡單的 ExecutorService 實(shí)現(xiàn),也就是上面所說的四種Executors線程池,但是 ThreadPoolExecutor 提供的功能遠(yuǎn)不止于此。 不過在Java doc中,并不提倡我們直接使用ThreadPoolExecutor,而是使用Executors類中提供的幾個(gè)靜態(tài)方法來創(chuàng)建線程池。
我們可以在創(chuàng)建 ThreadPoolExecutor 實(shí)例時(shí)指定活動(dòng)線程的數(shù)量,我們也可以限制線程池的大小并且創(chuàng)建我們自己的 RejectedExecutionHandler 實(shí)現(xiàn)來處理不能適應(yīng)工作隊(duì)列的工作。

合理配置線程池大小

遵循兩原則:
1、如果是CPU密集型任務(wù),就需要盡量壓榨CPU,參考值可以設(shè)為 NCPU+1
2、如果是IO密集型任務(wù),參考值可以設(shè)置為2*NCPU
當(dāng)然,這只是一個(gè)參考值,具體的設(shè)置還需要根據(jù)實(shí)際情況進(jìn)行調(diào)整,比如可以先將線程池大小設(shè)置為參考值,再觀察任務(wù)運(yùn)行情況和系統(tǒng)負(fù)載、資源利用率來進(jìn)行適當(dāng)調(diào)整。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容