Java多線程-線程池的使用

線程池的優勢

  • 線程頻繁的建立=>銷燬=>建立對系統對開銷很大,使用線程池能夠避免重複的開銷
  • 方便複用,提升相應速度
  • 線程的建立於執行徹底分開,方便維護,下降耦合

線程池的實現原理

池化技術

一說到線程池天然就會想到池化技術java

其實所謂池化技術,就是把一些可以複用的東西放到池中,避免重複建立、銷燬的開銷,從而極大提升性能。併發

常見池化技術的例如:性能

  • 線程池
  • 內存池
  • 鏈接池

Java中的實現

官方接口

JDK 1.5 推出了三大API用來建立線程:線程

  • Executors.newCachedThreadPool():無限線程池(最大21億)
  • Executors.newFixedThreadPool(nThreads):固定大小的線程池
  • Executors.newSingleThreadExecutor():單個線程的線程池

這三個API的底層其實都是由同一個類實現的:ThreadPoolExecutorcode

public static ExecutorService newCachedThreadPool() {
  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                60L, TimeUnit.SECONDS,
                                new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
  return new ThreadPoolExecutor(nThreads, nThreads,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
  return new FinalizableDelegatedExecutorService
    (new ThreadPoolExecutor(1, 1,
                            0L, TimeUnit.MILLISECONDS,
                            new LinkedBlockingQueue<Runnable>()));
}

ThreadPoolExecutor

七大參數

ThreadPoolExecutor類主要有如下七個參數:對象

  • int corePoolSize: 核心線程池大小
  • int maximumPoolSize: 最大核心線程池大小
  • long keepAliveTime: 線程空閒後的存活時間
  • TimeUnit unit: 超時單位
  • BlockingQueue<Runnable> workQueue: 阻塞隊列
  • ThreadFactory threadFactory: 線程工廠:建立線程的,通常默認
  • RejectedExecutionHandler handle: 拒絕策略

四種拒絕策略

拒絕策略就是當隊列滿時,線程如何去處理新來的任務。繼承

Java內置了四種拒絕策略:接口

  • ThreadPoolExecutor.CallerRunsPolicy()隊列

  • ThreadPoolExecutor.AbortPolicy()內存

  • ThreadPoolExecutor.DiscardPolicy()

  • ThreadPoolExecutor.DiscardOldestPolicy()

CallerRunsPolicy(調用者運行策略)

功能:只要線程池沒有關閉,就由提交任務的當前線程處理。

使用場景:通常在不容許失敗、對性能要求不高、併發量較小的場景下使用。

AbortPolicy(停止策略)

功能:當觸發拒絕策略時,直接拋出拒絕執行的異常

使用場景:ThreadPoolExecutor中默認的策略就是AbortPolicy,因爲ExecutorService接口的系列ThreadPoolExecutor都沒有顯示的設置拒絕策略,因此默認的都是這個。

DiscardPolicy(丟棄策略)

功能:直接丟棄這個任務,不觸發任何動做

使用場景:提交的任務可有可無,通常用的少。

DiscardOldestPolicy(棄老策略)

功能:彈出隊列頭部的元素,而後嘗試執行,至關於排隊的時候把第一我的打死,而後本身代替

使用場景:發佈消息、修改消息相似場景。當老消息還未執行,此時新的消息又來了,這時未執行的消息的版本比如今提交的消息版本要低就能夠被丟棄了。

線程池中的狀態

img

  • RUNNING 天然是運行狀態,指能夠接受任務執行隊列裏的任務
  • SHUTDOWN 指調用了 shutdown() 方法,再也不接受新任務了,可是隊列裏的任務得執行完畢。
  • STOP 指調用了 shutdownNow() 方法,再也不接受新任務,同時拋棄阻塞隊列裏的全部任務並中斷全部正在執行任務。
  • TIDYING 全部任務都執行完畢,在調用 shutdown()/shutdownNow() 中都會嘗試更新爲這個狀態。
  • TERMINATED 終止狀態,當執行 terminated() 後會更新爲這個狀態。

處理流程

提交一個任務到線程池中,線程池的處理流程以下:

  • 判斷線程池裏的核心線程是否都在執行任務

    • 是:進入下個流程。
    • 否:調用/建立一個新的核心線程來執行任務。
  • 線程池判斷工做隊列是否已滿

    • 是:進入下個流程。
    • 否:將新提交的任務存儲在這個工做隊列裏。
  • 判斷線程池裏的全部線程是否都處於工做狀態

    • 是:交給拒絕策略來處理這個任務。
    • 否:調用/建立一個新的工做線程來執行任務。

具體使用

建立

不過最好不要使用Executors來建立線程,緣由以下(參考自——阿里巴巴Java開發手冊):

  • FixedThreadPoolSingleThreadPool: 容許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而致使 OOM
  • CachedThreadPool: 容許的建立線程數量爲 Integer.MAX_VALUE,可能會建立大量的線程,從而致使 OOM

推薦使用ThreadPoolExecutor類自行建立

// 自定義線程池
ExecutorService threadPool = new ThreadPoolExecutor(
  2,
  Runtime.getRuntime().availableProcessors(),//CPU的核心數,適合CPU密集型任務
  3,
  TimeUnit.SECONDS,
  new LinkedBlockingDeque<>(3),
  Executors.defaultThreadFactory(),
  new ThreadPoolExecutor.DiscardOldestPolicy());

合理配置線程

線程池不是越大越好,要根據任務類型合理進行配置

  • IO 密集型任務:儘量的多配置線程
  • CPU 密集型任務:(大量複雜的運算)應當分配較少的線程

執行

有兩個方法能夠執行任務executesubmit

  • execute提交沒有返回值,不能判斷是否執行成功。
  • submit會返回一個Future對象,經過Futureget()方法來獲取返回值。

區別:

  • 接收的參數不同
    • execute提交的方式只能提交一個Runnable的對象
    • submit有三種
  • submit有返回值,而execute沒有
  • submit方便Exception處理
  • executeExecutor接口中惟必定義的方法
  • submitExecutorService(該接口繼承Executor)中定義的方法

關閉

線程池使用完畢,須要對其進行關閉,有兩種方法

  • shutdown():再也不繼續接收新的任務,執行完成已有任務後關閉

  • shutdownNow():直接關閉,若果有任務嘗試中止

相關文章
相關標籤/搜索