創建自己的多線程池類
本文通過ThreadGroup創建線程池類。
- 線程池介紹
- 自定義線程
- JDK自帶線程池分析
線程池介紹
線程池就是預先創建一些工作線程,它們不斷從工作隊列中取出任務,然後完成任務。當工作線程執行完一個任務後,就會繼續執行工作隊列中的下一個任務。
線程池優點
- 減少了線程創建和銷燬的次數,每個工作線程都可以一直被重用,能執行多個任務。
可以根據系統的承載能力,自由調整線程池中線程的數目。防止因爲消耗過量資源而導致系統崩潰。
自定義線程
代碼如下:
public class ThreadPool extends ThreadGroup{ private boolean isClosed = false; private LinkedList<Runnable> workQueue; private static int threadPoolID; private int threadID; public ThreadPool(int poolSize){ super("ThreadPool-"+(threadPoolID++)); setDaemon(true); workQueue = new LinkedList<>();//創建工作隊列 for(int i=0;i<poolSize;i++){ new WorkThread().start();//創建並啓動工作線程 } } /** * 獲取task * @return * @throws InterruptedException */ protected synchronized Runnable getTask() throws InterruptedException { while (workQueue.size()==0){ if(isClosed){ return null; } wait(); //如果任務隊列中沒有任務,就等待任務 } return workQueue.removeFirst(); } /** * 關閉線程池 */ public synchronized void close(){ if(!isClosed){ isClosed = true; workQueue.clear();//清空任務隊列 interrupt();//中斷所有的工作線程,該方法繼承ThreadGroup類 } } public void join(){ synchronized (this){ isClosed = true; notifyAll();//喚醒還在getTask()方法中等待任務的工作線程 } Thread[] threads = new Thread[activeCount()]; int count = enumerate(threads);//後的線程組中當前所有活着的工作線程 for(int i=0;i<count;i++){//等待所有工作線程結束 try { threads[i].join(); //等待工作線程運行結束 } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 執行任務 * @param task */ public synchronized void execute(Runnable task){ if(isClosed){ throw new IllegalStateException(); } if(task!=null){ workQueue.add(task); notify();//喚醒正在getTask()方法中等待任務的工作線程 } } private class WorkThread extends Thread{ public WorkThread(){ super(ThreadPool.this,"WorkThread-"+(threadID++)); } @Override public void run() { while(!isInterrupted()){//判斷線程是否被中斷 Runnable task = null; try { task = getTask(); } catch (InterruptedException e) { e.printStackTrace(); } if(task==null){ return; } try { task.run(); }catch (Exception e){ e.printStackTrace(); } } } } }
ThreadPool的測試類:
public class ThreadPoolTest {
public static void main(String[] args) {
int numTask = 10;
int poolSize = 4;
ThreadPool threadPool = new ThreadPool(poolSize);
for(int i=0;i<numTask;i++){
threadPool.execute(createTask(i));
}
threadPool.close();
}
private static Runnable createTask(final int i) {
return new Runnable() {
@Override
public void run() {
System.out.println("Task "+ i +":start");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task "+ i +":end");
}
};
}
}
代碼分析
在ThreadPool類中定義了一個LinkedList類型的workQueue成員變量,它表示工作隊列,用來存放線程池要執行的任務,每個任務都是Runnable實例。ThreadPool的測試方法只需調用execute()方法,就能向ThreadPool提交任務。
在ThreadPool類的構造方法中,會創建並啓動若干工作線程,工作線程的數目由參數poolSize決定。WorkThread表示工作線程,它是ThreadPool類的內部類。工作線程從工作隊列中獲取一個任務,接着執行該任務,然後再從工作隊列中取出下一個任務並執行它,如此反覆。
在execute()方法中:先判斷該線程池是否已經關閉,如果關閉拋出IllegalStateException異常。如果沒有關閉的話,將任務加到workQueue中,然後在喚醒getTask()方法中獲取任務的線程。
getTask()方法實現:如果隊列爲空且線程池已關閉,則返回null。如果隊列爲空線程池沒有關閉,則在此等待。如果隊列中有任務則取出第一個將其返回。
- join()和close()方法都可用於關閉線程池。join()方法確保在關閉線程之前,工作線程把隊列中的所有任務都執行完。而close()方法則情況隊列,並中斷所有工作線程。
測試類執行結果:
JDK自帶線程池分析
測試JDK自帶線程
代碼如下:
public class ThreadPoolTest2 {
public static void main(String[] args) {
int poolSize = 4;
ExecutorService executorService = Executors.newFixedThreadPool(poolSize);
for(int i=0;i<10;i++) {
executorService.execute(createTask(i));
}
//executorService.shutdownNow(); //與自定義的線程池的close()方法類似
executorService.shutdown(); //與自定義的線程池的join()方法類似
}
private static Runnable createTask(final int taskId) {
return new Runnable() {
@Override
public void run() {
System.out.println("Task " + taskId + ":start");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + ":end");
}
};
}
}
執行結果如下:
在上面代碼中通過ExecutorService executorService = Executors.newFixedThreadPool(poolSize);
創建線程池,該方法時創建一個固定數目的線程池。
進入newFixedThreadPool
方法內,內容如下:
這個方法這是個幌子,在進入ThreadPoolExecutor
類中 的另一個構造方法點擊 this
進入 查看各個參數表示什麼意思:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param 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.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
參數 corePoolSize :核心線程池線程數目。
參數 maximumPoolSize :最大線程池線程數。
參數 keepAliveTime :空閒時間,如果線程池中的線程數大於corePoolSize的話,則這些多出來的線程在空閒時間超過keepAliveTime時將會銷燬。
參數 unit :keepAliveTime參數的單位。
參數 workQueue :工作隊列。工作隊列支持三種策略:
- 直接提交。工作隊列的默認選項是
SynchronousQueue
一種阻塞隊列。其中每個插入操作必須等待另一個線程的對應移除操作 ,反之亦然。同步隊列沒有任何內部容量,甚至連一個隊列的容量都沒有。此策略可以避免在處理可能具有內部依賴性的請求集時出現鎖。 - 無界隊列。使用無界隊列(例如,不具有預定義容量的
LinkedBlockingQueue
)將導致在所有 corePoolSize 線程都忙於新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize。(因此,maximumPoolSize 的值也就無效了。)當每個任務完全獨立於其他任務,即任務執行互不影響時,適合於使用無界隊列; - 有界隊列。當使用有限的 maximumPoolSizes 時,有界隊列(如
ArrayBlockingQueue
)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致降低吞吐量。
參數 threadFactory :線程工廠。使用 ThreadFactory
創建新線程。如果沒有另外說明,則在同一個 ThreadGroup
中一律使用 Executors.defaultThreadFactory()
創建線程,並且這些線程具有相同的 NORM_PRIORITY
優先級和非守護進程狀態。
參數 handler :被拒絕的任務處理的handler。當 Executor
已經關閉,並且 Executor
將有限邊界用於最大線程和工作隊列容量,且已經飽和時,在方法 execute(java.lang.Runnable)
中提交的新任務將被 拒絕。在默認的 ThreadPoolExecutor.AbortPolicy
中,處理程序遭到拒絕將拋出運行時異常 RejectedExecutionException
。
類圖
Executor
、ExecutorService
、Executors
三個類直接的關係:
總結:
其實我們自己定義的線程池也是借鑑JDK自帶的線程池的。在創建一個線程池的時候,都有這幾個必要元素:線程池大小、線程存活時間、線程工廠、工作隊列、任務拒絕處理方法。
在使用JDK的線程池中,我們只用到兩個類,一個是Executors
和ExecutorService
這個是接口。其實還有一個接口很重要,因爲它是execute()
方法的申明類,也是ExecutorService
的父接口,就是 Executor
。這三個類各司其職:
類名 | 作用 |
---|---|
Executor | 線程池類,它的execute()方法用來執行Runnable類型的任務。 |
ExecutorService | 是Executor的子接口,申明瞭一些管理線程池的方法。比如shutdow() |
Executors | Executors包含一些靜態方法,它主要負責創建各種類型的ExecutorService實例。 |
歡迎關注微信公衆號 在路上的coder
每天分享優秀的Java技術文章!
掃描二維碼關注: