服務端的程序,例如數據庫服務器和Web服務器,每次收到客戶端的請求,都會建立一個線程來處理這些請求。html
建立線程的方式又不少,例如繼承Thread類、實現Runnable或者Callable接口等。java
經過建立新的線程來處理客戶端的請求,這種看起來很容易的方法,實際上是有很大弊端且有很高的風險的。git
俗話說,簡單的路越走越困難,困難的路越走越簡單,就是這個道理。github
建立和銷燬線程,會消耗大量的服務器資源,甚至建立和銷燬線程消耗的時間比線程自己處理任務的時間還要長。shell
因爲啓動線程須要消耗大量的服務器資源,若是建立過多的線程會形成系統內存不足(run out of memory),所以限制線程建立的數量十分必要。數據庫
線程池通俗來說就是一個取出和放回提早建立好的線程的池子,概念上,相似數據庫的鏈接池。編程
那麼線程池是如何發揮做用的呢?緩存
實際上,線程池是經過重用以前建立好線程來處理當前任務,來達到大大下降線程頻繁建立和銷燬致使的資源消耗的目的。服務器
A thread pool reuses previously created threads to execute current tasks and offers a solution to the problem of thread cycle overhead and resource thrashing. Since the thread is already existing when the request arrives, the delay introduced by thread creation is eliminated, making the application more responsive.
下面總結一下開篇對於線程池的一些介紹。多線程
但到底怎麼使用線程池呢?線程池真的這麼簡單好用嗎?線程池使用的過程當中有沒有什麼坑?
不要着急,下面就結合具體的示例,跟你講解各類使用線程池的姿式,以及這些姿式爽在哪裏,痛在哪裏。
準備好紙巾,咳咳...,是筆記本,濤哥要跟你開講啦!
java.util.concurrent.Executors
是JDK的併發包下提供的一個工廠類(Factory)和工具類(Utility)。
Executors
提供了關於Executor
, ExecutorService
, ScheduledExecutorService
, ThreadFactory
和 Callable
相關的工廠方法和工具方法。
Executor
是一個執行提交的Runnable Tasks
的對象,它有一個execute
方法,參數是Runnable
。當執行execute
方法之後,會在將來某個時間,經過建立線程或者使用線程池中的線程的方式執行參數中的任務。用法以下:
Executor executor = anExecutor; executor.execute(new RunnableTask1()); executor.execute(new RunnableTask2());
ExecutorService
繼承了Executor
,並提供了更多有意思的方法,好比shutdown
方法會讓ExecutorService
拒絕建立新的線程來執行task。
//建立固定線程數量的線程池 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10); //建立一個線程池,該線程池會根據須要建立新的線程,但若是以前建立的線程可使用,會重用以前建立的線程 ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); //建立一個只有一個線程的線程池 ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
下面我就建立5個Task,並經過一個包含3個線程的線程池來執行任務。咱們一塊兒看下會發生什麼。
☝
ThreadPoolExample1
就是咱們的測試類,下面全部的內部類、常量和方法都寫在這個測試類裏。
package net.ijiangtao.tech.concurrent.jsd.threadpool; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample1 { }
Task
內部類執行了兩次for循環,並在每次循環執行結束之後 sleep 1秒鐘。
// Task class to be executed (Step 1) static class Task implements Runnable { private String name; public Task(String s) { name = s; } // Prints task name and sleeps for 1s // This Whole process is repeated 2 times public void run() { try { for (int i = 0; i <= 1; i++) { if (i == 0) { //prints the initialization time for every task printTimeMsg("Initialization"); } else { // prints the execution time for every task printTimeMsg("Executing"); } Thread.sleep(1000); } System.out.println(name + " complete"); } catch (InterruptedException e) { e.printStackTrace(); } } private void printTimeMsg(String state) { Date d = new Date(); SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss"); System.out.println(state+" Time for"+ " task name - " + name + " = " + ft.format(d)); } }
建立一個固定線程數的線程池。
// Maximum number of threads in thread pool static final int MAX_T = 3; // creates a thread pool with MAX_T no. of // threads as the fixed pool size(Step 2) private static final ExecutorService pool = Executors.newFixedThreadPool(MAX_T);
建立5個任務,並經過線程池的線程執行這些任務。
public static void main(String[] args) { // creates five tasks Runnable r1 = new Task("task 1"); Runnable r2 = new Task("task 2"); Runnable r3 = new Task("task 3"); Runnable r4 = new Task("task 4"); Runnable r5 = new Task("task 5"); // passes the Task objects to the pool to execute (Step 3) pool.execute(r1); pool.execute(r2); pool.execute(r3); pool.execute(r4); pool.execute(r5); // pool shutdown ( Step 4) pool.shutdown(); }
執行結果以下。
Initialization Time for task name - task 1 = 12:39:44 Initialization Time for task name - task 2 = 12:39:44 Initialization Time for task name - task 3 = 12:39:44 Executing Time for task name - task 3 = 12:39:45 Executing Time for task name - task 1 = 12:39:45 Executing Time for task name - task 2 = 12:39:45 task 2 complete Initialization Time for task name - task 4 = 12:39:46 task 3 complete Initialization Time for task name - task 5 = 12:39:46 task 1 complete Executing Time for task name - task 5 = 12:39:47 Executing Time for task name - task 4 = 12:39:47 task 5 complete task 4 complete
從輸出的結果咱們能夠看到,5個任務在包含3個線程的線程池執行。
因爲線程的執行有必定的隨機性,以及不一樣機器的資源狀況不一樣,每次的執行結果,可能會有差別。
下面是我第二次執行的結果。
Initialization Time for task name - task 1 = 12:46:33 Initialization Time for task name - task 3 = 12:46:33 Initialization Time for task name - task 2 = 12:46:33 Executing Time for task name - task 2 = 12:46:34 Executing Time for task name - task 3 = 12:46:34 Executing Time for task name - task 1 = 12:46:34 task 3 complete task 2 complete task 1 complete Initialization Time for task name - task 4 = 12:46:35 Initialization Time for task name - task 5 = 12:46:35 Executing Time for task name - task 4 = 12:46:36 Executing Time for task name - task 5 = 12:46:36 task 5 complete task 4 complete
task 1 2 3 得到線程資源,task 4 5排隊等待:
task 1 2 3 執行結束,task 4 5得到線程資源,線程池中有一個線程處於空閒狀態:
但規律是相同的,那就是線程池會將本身的線程資源貢獻出來,若是任務數超出了線程池的線程數,就會阻塞並排隊等待有可用的線程資源之後執行。
也就是線程池會保證你的task在未來(Future)的某個時間執行,但並不能保證什麼時間會執行。
相信你如今對於ExecutorService
的invokeAll
方法,能夠執行一批task並返回一個Future
集合,就會有更深刻的理解了。
List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException
經過ExecutorService
線程池執行task的過程以下圖所示,超出線程池線程數量的task將會在BlockingQueue
排隊等待得到線程資源的機會。
關於併發編程中的Futrue,筆者有一篇文章(Java併發編程-Future系列之Future的介紹和基本用法)專門介紹,請經過下面任意的連接移步欣賞:
本教程帶領你們瞭解了線程池的來由、概念和基本用法,相信你們看完,之後就再也不只會傻傻地new Thread
了。
本節只是線程池的入門,下面會介紹關於線程池的更多武功祕籍
,但願你們持續關注,有所獲益。
喜歡請點贊轉發,若是你們對這個系列感興趣,我會繼續更新的。
👍
Concurrent-ThreadPool-線程池拒絕策略RejectedExecutionHandler
Concurrent-ThreadPool-ThreadPoolExecutor裏面4種拒絕策略
Concurrent-ThreadPool-線程池ThreadPoolExecutor構造方法和規則
Concurrent-ThreadPool-線程池的成長之路
Concurrent-ThreadPool-LinkedBlockingQueue和ArrayBlockingQueue的異同
Concurrent-ThreadPool-Thread Pools in Java
Concurrent-ThreadPool-java-thread-pool