为什么线程池里的方法会执行两次_新手一看就懂的线程池
作者:碼農田小齊
來源:https://www.cnblogs.com/nycsde/p/14003888.html
那相信大家也能感受到,其實用多線程是很麻煩的,包括線程的創建、銷毀和調度等等,而且我們平時工作時好像也并沒有這樣來 new 一個線程,其實是因為很多框架的底層都用到了線程池。
線程池是幫助我們管理線程的工具,它維護了多個線程,可以降低資源的消耗,提高系統的性能。
并且通過使用線程池,我們開發人員可以更好的把精力放在任務代碼上,而不去管線程是如何執行的,實現任務提交和執行的解藕。
本文將從是何、為何、如何的角度來講解線程池:
線程池 Thread Pool
線程池是一種池化的技術,類似的還有數據庫連接池、HTTP 連接池等等。
池化的思想主要是為了減少每次獲取和結束資源的消耗,提高對資源的利用率。
比如在一些偏遠地區打水不方便的,大家會每段時間把水打過來存在池子里,這樣平時用的時候就直接來取就好了。
線程池同理,正是因為每次創建、銷毀線程需要占用太多系統資源,所以我們建這么一個池子來統一管理線程。用的時候從池子里拿,不用了就放回來,也不用你銷毀,是不是方便了很多?
Java 中的線程池是由 juc 即 java.util.concurrent 包來實現的,最主要的就是 ThreadPoolExecutor 這個類。具體怎么用我們下文再說。
線程池的好處
在多線程的第一篇文章中我們說過,進程會申請資源,拿來給線程用,所以線程是很占用系統資源的,那么我們用線程池來統一管理線程就能夠很好的解決這種 資源管理問題 。
比如因為不需要創建、銷毀線程,每次需要用的時候我就去拿,用完了之后再放回去,所以節省了很多資源開銷,可以提高系統的運行速度。
而統一的管理和調度,可以合理分配內部資源,根據系統的當前情況調整線程的數量。
那總結來說有以下 3 個好處:
說了這么多,終于到了今天的重點,我們來看下究竟怎么用線程池吧~
線程池的實現
Java 給我們提供了 Executor 接口來使用線程池。
我們常用的線程池有這兩大類:
- ThreadPoolExecutor
- ScheduledThreadPoolExecutor
它倆的區別呢,就是第一個是普通的,第二個是可以定時執行的。
當然還有其他線程池,比如 JDK 1.7 才出現的 ForkJoinPool ,可以把大任務分割成小任務來執行,最后再大一統。
那么任務提交到一個線程池之后,它會經歷一個怎樣的過程呢?
執行過程
線程池在內部實際上采用了生產者消費者模型(還不清楚這個模型的在文章開頭有改文章的鏈接)將線程和任務解藕,從而使線程池同時管理任務和線程。
當任務提交到線程池里之后,需要經過以下流程:
ThreadPoolExecutor
我們主要說下 ThreadPoolExecutor ,它是最常用的線程池。
這里我們可以看到,這個類里有 4 個構造方法,點進去仔細看,其實前三個都 call 了最后一個,所以我們只需要看最后一個就好。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { ...}這里我們來仔細看下這幾個參數:
corePoolSizethe number of threads to keep in the pool, even if they are idle, unless { @code allowCoreThreadTimeOut} is set
maximumPoolSize the maximum number of threads to allow in the pool
keepAliveTime when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
unitthe time unit for the { @code keepAliveTime} argument
workQueue the queue to use for holding tasks before they are executed.
threadFactory the factory to use when the executor creates a new thread
handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached
所以我們可以通過自己傳入這 7 個參數構造線程池,當然了,貼心的 Java 也給我們包裝好了幾類線程池可以很方便的拿來使用。
- newCachedThreadPool
- newFixedThreadPool
- newSingleThreadExecutor
我們具體來看每個的含義和用法。
newCachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue());}這里我們可以看到,
Integer.MAX_VALUE它的適用場景在源碼里有說:
These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks.來看怎么用:
public class newCacheThreadPool { public static void main(String[] args) { // 創建一個線程池 ExecutorService executorService = Executors.newCachedThreadPool(); // 向線程池提交任務 for (int i = 0; i < 50; i++) { executorService.execute(new Task());//線程池執行任務 } executorService.shutdown(); }}執行結果:
可以很清楚的看到,線程 1、2、3、5、6 都很快重用了。
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());}這個線程池的特點是:
它的適用場景是:
Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.public class FixedThreadPool { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 200; i++) { executorService.execute(new Task()); } executorService.shutdown(); }}這里我限制了線程池里最多有 10 個線程,哪怕有 200 個任務需要執行,也只有 1-10 這 10 個線程可以運行。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));}這個線程池顧名思義,里面只有 1 個線程。
適用場景是:
Creates an Executor that uses a single worker thread operating off an unbounded queue.我們來看下效果。
public class SingleThreadPool { public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 100; i++) { executorService.execute(new Task()); } executorService.shutdown(); }}這里在出結果的時候我能夠明顯的感覺到有些卡頓,這在前兩個例子里是沒有的,畢竟這里只有一個線程在運行嘛。
小結
所以在使用線程池時,其實都是調用的 ThreadPoolExecutor 這個類,只不過傳遞的不同參數。
這里要特別注意兩個參數:
workQueuehandler那我們發現,在上面的 3 個具體線程池里,其實都沒有設定 handler ,這是因為它們都使用了 defaultHandler 。
/** * The default rejected execution handler */private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();在 ThreadPoolExecutor 里有 4 種拒絕策略,這 4 種策略都是 implements 了 RejectedExecutionHandler :
所以這 3 種線程池都使用的默認策略也就是第一種,光明正大的拒絕。
好了以上就是本文的所有內容了。當然線程池還有很多知識點,比如 execute() 和 submit() 方法,線程池的生命周期等等。
但隨著閱讀量的逐漸走低,齊姐意識到了這似乎有什么誤會,所以這篇文章是多線程系列的最后一篇了。
本文已收錄至我的 Github 上: https://github.com/xiaoqi6666/NYCSDE
作者:碼農田小齊
來源:https://www.cnblogs.com/nycsde/p/14003888.html
總結
以上是生活随笔為你收集整理的为什么线程池里的方法会执行两次_新手一看就懂的线程池的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dijkstra邻接表_掌握算法-图论-
- 下一篇: clickhouse 航空数据_Clic