您好,登錄后才能下訂單哦!
一、引言
池的概念大家并不陌生,數據庫連接池、線程池等...大體來說,有三個優點:
以上是 “池化” 技術的相同特點,至于他們之間的不同點這里不講,兩者都是為了提高性能和效率,拋開實際做連連看找不同,沒有意義。
同樣,類比于線程池來說:
重復利用線程池中已經創建的線程,相比之下省去了線程創建和銷毀的性能消耗。
當有任務創建時,不必等待線程創建,可以立即執行。
使用線程池,可以對線程統一管理,對線程的執行狀態做統一監控。
二、線程池的使用
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
1、關鍵參數
當向線程池中提交一個任務時,如果線程池中的線程數量小于核心線程數,即使存在空閑線程,也會新建一個線程來執行當前任務,直到線程數量大于或等于核心線程數。
當任務隊列滿了,線程池中的線程數量小于最大線程數時,創建新線程執行任務。對于無界隊列,忽略該參數。
大于核心線程數的那一部分線程的存活時間,如果這部分線程空閑超過這段時間,則進行銷毀。
線程池中的線程數大于核心線程數時,將任務放入此隊列等待執行。
用于創建線程,工廠使用 new Threa() 的方式創建線程,并為每個線程做統一規則的命名:pool-m-thread-n(m為線程池的編號,n為線程池內的線程編號)。
當線程池和隊列都滿了,則根據此策略處理任務。
2、任務隊列類型
名稱 | 描述 |
---|---|
ArrayBlockingQueue | 基于數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。 |
LinkedBlockingQueue | 基于鏈表結構的阻塞隊列,此隊列按 FIFO (先進先出) 排序元素,吞吐量通常要高于 ArrayBlockingQueue。Executors.newFixedThreadPool( ) 使用了這個隊列。 |
SynchronousQueue | 不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處于阻塞狀態,吞吐量通常要高于 LinkedBlockingQueue,靜態工廠方法 Executors.newCachedThreadPool( ) 使用了這個隊列。 |
PriorityBlockingQueue | 具有優先級的無限阻塞隊列。 |
3、飽和策略類型
策略名稱 | 特性 |
---|---|
AbortPolicy | 默認的飽和策略,直接拋出 RejectedExecutionException 異常 |
DiscardPolicy | 不處理,直接丟棄任務 |
CallerRunsPolicy | 使用調用者的線程執行任務 |
DiscardOldestPolicy | 丟棄隊列里最近的一個任務,執行當前任務 |
同時,還可以自行實現 RejectedExecutionHandler 接口來自定義飽和策略,比如記錄日志、持久化等等。
void execute(Runnable command)
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build(); ExecutorService executor = new ThreadPoolExecutor( 10, 1000, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); executor.execute( () -> { System.out.println(1111); });
注意使用 execute 方法提交任務時,沒有返回值。
Future<?> submit(Runnable task)
Future<Integer> future = executor.submit(() -> { return 1 + 1; }); Integer result = future.get();
還可以使用 submit 方法提交任務,該方法返回一個 Future 對象,通過 Future#get( ) 方法可以獲得任務的返回值,該方法會一直阻塞知道任務執行完畢。還可以使用 Future#get(long timeout, TimeUnit unit) 方法,該方法會阻塞一段時間后立即返回,而這時任務可能沒有執行完畢。
5、關閉線程池
ThreadPoolExecutor 提供了 shutdown( ) 和 shutdownNow( ) 兩個方法關閉線程池。原理是首先遍歷線程池的工作線程,依次調用 interrupt( ) 方法中斷線程,這樣看來如果無法響應中斷的任務就不能終止。
兩者區別是:
shutdownNow( )
shutdown( )
如果調用了其中一種方法,isShutdown 方法就會返回 true。當所有的任務都已關閉后, 才表示線程池關閉成功,這時調用 isTerminaed 方法會返回 true。實際應用中可以根據任務是否 一定要執行完畢 的特性,決定使用哪種方法關閉線程池。
6、合理的配置線程池
通常我們可以 根據 CPU 核心數量來設計線程池數量 。
可以通過 Runtime.getRuntime().availableProcessors() 方法獲得當前設備的物理核心數量。值得注意的是,如果應用運行在一些 docker 或虛擬機容器上時,該方法取得的是當前物理機的 CPU 核心數。
其中 n 為 CPU 核心數量。
為什么加 1:即使當計算密集型的線程偶爾由于缺失故障或者其他原因而暫停時,這個額外的線程也能確保 CPU 的時鐘周期不會被浪費。
三、線程池的運行過程
當提交一個新任務時,線程池的處理步驟:
線程池的源碼比較簡單易懂,感興趣的小伙伴可以自行查看 java.util.concurrent.ThreadPoolExecutor ,在線程池中每個任務都被包裝為一個一個的 Worker ,下面簡單看下 Worker 的 run( ) 方法:
try { while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); }
可以看到不斷的循環取出 Task 并執行,而在任務的執行前后,有 beforeExecute 和 afterExecute 方法,我們可以實現兩個方法實現一些監控邏輯。除此之外還可以集合線程池的一些屬性或者重寫 terminated() 方法在線程池關閉時進行監控。
四、常見的幾種線程池實現
在 Executors 中提供了集中常見的線程池,分別應用在不同的場景。
上面幾種線程池的特性主要依賴于 ThreadPoolExecutor 的幾個參數來實現,不同的核心線程數量,以及不同類型的阻塞隊列,同時我們還可以自行實現自己的線程池滿足業務需求。
值得注意的是,并不推薦使用 Executors 創建線程池,詳見下:
Executors.newFixedThreadPool(int nThread)
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
繼續來看 LinkedBlockingQueue :
public LinkedBlockingQueue() { this(Integer.MAX_VALUE); } public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); }
可以看到使用 LinkedBlockingQueue 創建的是 Integer.MAX_VALUE 大小的隊列,會堆積大量的請求,從而造成 OOM
Executors.newSingleThreadExexutor( )
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
同樣,使用的 LinkedBlockingQueue ,一樣的情況
Executors.newCachedThreadPool( )
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
代碼課件線程池使用的最大線程數是 Integer.MAX_VALUE ,可能會創建大量線程,導致 OOM
Executors.newScheduleThreadPool()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
和上面是一樣的問題,最大線程數是 Integer.MAX_VALUE
所以原則上來說禁止使用 Executors 創建線程池, 而使用 ThreadPoolExecutor 的構造函數來創建線程池。
五、結語
線程池在開發中還是比較常見的,結合不同的業務場景,結合最佳實踐配置正確的參數,可以幫助我們的應用性能得到提升。
以上就是談談Java 線程池的詳細內容,更多關于Java 線程池的資料請關注億速云其它相關文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。