【Java线程】线程池的原理和实现
1、為什么要使用線程池?
線程池是Java5提供的一個新技術,方便我們快速簡潔的定義線程池。
諸如 Web 服務器、數據庫服務器、文件服務器或郵件服務器之類的許多服務器應用程序都面向處理來自某些遠程來源的大量短小的任務。請求以某種方式到達服務器,這種方式可能是通過網絡協議(例如 HTTP、FTP 或 POP)、通過 JMS 隊列或者可能通過輪詢數據庫。不管請求如何到達,服務器應用程序中經常出現的情況是:單個任務處理的時間很短而請求的數目卻是巨大的。
構建服務器應用程序的一個過于簡單的模型應該是:每當一個請求到達就創建一個新線程,然后在新線程中為請求服務。實際上,對于原型開發這種方法工作得很好,但如果試圖部署以這種方式運行的服務器應用程序,那么這種方法的嚴重不足就很明顯。每個請求對應一個線程(thread-per-request)方法的不足之一是:為每個請求創建一個新線程的開銷很大;為每個請求創建新線程的服務器在創建和銷毀線程上花費的時間和消耗的系統資源要比花在處理實際的用戶請求的時間和資源更多。
除了創建和銷毀線程的開銷之外,活動的線程也消耗系統資源。在一個 JVM 里創建太多的線程可能會導致系統由于過度消耗內存而用完內存或“切換過度”。為了防止資源不足,服務器應用程序需要一些辦法來限制任何給定時刻處理的請求數目。
線程池為線程生命周期開銷問題和資源不足問題提供了解決方案。通過對多個任務重用線程,線程創建的開銷被分攤到了多個任務上。其好處是,因為在請求到達時線程已經存在,所以無意中也消除了線程創建所帶來的延遲。這樣,就可以立即為請求服務,使應用程序響應更快。而且,通過適當地調整線程池中的線程數目,也就是當請求的數目超過某個閾值時,就強制其它任何新到的請求一直等待,直到獲得一個線程來處理為止,從而可以防止資源不足。
2、線程池的實現原理
多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。
假設一個服務器完成一項任務所需時間為:T1 創建線程時間,T2 在線程中執行任務的時間,T3 銷毀線程時間。
如果:T1 + T3 遠大于 T2,則可以采用線程池,以提高服務器性能。
一個線程池包括以下四個基本組成部分:
1、線程池管理器(ThreadPool):用于創建并管理線程池,包括 創建線程池,銷毀線程池,添加新任務;
2、工作線程(PoolWorker):線程池中線程,在沒有任務時處于等待狀態,可以循環的執行任務;
3、任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完后的收尾工作,任務的執行狀態等;
4、任務隊列(taskQueue):用于存放沒有處理的任務。提供一種緩沖機制。
線程池技術正是關注如何縮短或調整T1,T3時間的技術,從而提高服務器程序性能的。它把T1,T3分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T3的開銷了。
線程池不僅調整T1,T3產生的時間段,而且它還顯著減少了創建線程的數目,看一個例子:
假設一個服務器一天要處理50000個請求,并且每個請求需要一個單獨的線程完成。在線程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目,而如果服務器不利用線程池來處理這些請求則線程總數為50000。一般線程池大小是遠小于50000。所以利用線程池的服務器程序不會為了創建50000而在處理請求時浪費時間,從而提高效率。
代碼實現中并沒有實現任務接口,而是把Runnable對象加入到線程池管理器(ThreadPool),然后剩下的事情就由線程池管理器(ThreadPool)來完成了
/** * 線程管理器:創建線程,執行任務,銷毀線程,獲取線程基本信息 */ public final class ThreadPool { // 線程池中默認線程的個數為5 private static int worker_num = 5; // 工作線程 private WorkThread[] workThrads; // 已完成的任務個數 private static volatile int finished_task = 0; // 任務隊列,作為一個緩沖,List線程不安全 private List<Runnable> taskQueue = new LinkedList<Runnable>(); private static ThreadPool threadPool; // 創建具有默認線程個數的線程池 private ThreadPool() { this(5); } // 創建線程池,worker_num為線程池中工作線程的個數 private ThreadPool(int worker_num) { ThreadPool.worker_num = worker_num; workThrads = new WorkThread[worker_num]; for (int i = 0; i < worker_num; i++) { workThrads[i] = new WorkThread(); workThrads[i].start();// 開啟線程池中的線程 } } // 單態模式,獲得一個默認線程個數的線程池 public static ThreadPool getThreadPool() { return getThreadPool(ThreadPool.worker_num); } // 單態模式,獲得一個指定線程個數的線程池,worker_num(>0)為線程池中工作線程的個數 // worker_num<=0創建默認的工作線程個數 public static ThreadPool getThreadPool(int worker_num1) { if (worker_num1 <= 0) worker_num1 = ThreadPool.worker_num; if (threadPool == null) threadPool = new ThreadPool(worker_num1); return threadPool; } // 執行任務,其實只是把任務加入任務隊列,什么時候執行有線程池管理器覺定 public void execute(Runnable task) { synchronized (taskQueue) { taskQueue.add(task); taskQueue.notify(); } } // 批量執行任務,其實只是把任務加入任務隊列,什么時候執行有線程池管理器覺定 public void execute(Runnable[] task) { synchronized (taskQueue) { for (Runnable t : task) taskQueue.add(t); taskQueue.notify(); } } // 批量執行任務,其實只是把任務加入任務隊列,什么時候執行有線程池管理器覺決定 public void execute(List<Runnable> task) { synchronized (taskQueue) { for (Runnable t : task) taskQueue.add(t); taskQueue.notify(); } } // 銷毀線程池,該方法保證在所有任務都完成的情況下才銷毀所有線程,否則等待任務完成才銷毀 public void destroy() { while (!taskQueue.isEmpty()) {// 如果還有任務沒執行完成,就先睡會吧 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } // 工作線程停止工作,且置為null for (int i = 0; i < worker_num; i++) { workThrads[i].stopWorker(); workThrads[i] = null; } threadPool=null; taskQueue.clear();// 清空任務隊列 } // 返回工作線程的個數 public int getWorkThreadNumber() { return worker_num; } // 返回已完成任務的個數,這里的已完成是只出了任務隊列的任務個數,可能該任務并沒有實際執行完成 public int getFinishedTasknumber() { return finished_task; } // 返回任務隊列的長度,即還沒處理的任務個數 public int getWaitTasknumber() { return taskQueue.size(); } // 覆蓋toString方法,返回線程池信息:工作線程個數和已完成任務個數 @Override public String toString() { return "WorkThread number:" + worker_num + " finished task number:" + finished_task + " wait task number:" + getWaitTasknumber(); } /** * 內部類,工作線程 */ private class WorkThread extends Thread { // 該工作線程是否有效,用于結束該工作線程 private boolean isRunning = true; /* * 關鍵所在啊,如果任務隊列不空,則取出任務執行,若任務隊列空,則等待 */ @Override public void run() { Runnable r = null; while (isRunning) {// 注意,若線程無效則自然結束run方法,該線程就沒用了 synchronized (taskQueue) { while (isRunning && taskQueue.isEmpty()) {// 隊列為空 try { taskQueue.wait(20); } catch (InterruptedException e) { e.printStackTrace(); } } if (!taskQueue.isEmpty()) r = taskQueue.remove(0);// 取出任務 } if (r != null) { r.run();// 執行任務 } finished_task++; r = null; } } // 停止工作,讓該線程自然執行完run方法,自然結束 public void stopWorker() { isRunning = false; } } }測試一下:
public class TestThreadPool { public static void main(String[] args) { // 創建3個線程的線程池 ThreadPool t = ThreadPool.getThreadPool(3); t.execute(new Runnable[] { new Task(), new Task(), new Task() }); t.execute(new Runnable[] { new Task(), new Task(), new Task() }); System.out.println(t); t.destroy();// 所有線程都執行完成才destory System.out.println(t); } // 任務類 static class Task implements Runnable { private static volatile int i = 1; @Override public void run() {// 執行任務 System.out.println("任務 " + (i++) + " 完成"); } } }運行結果:
WorkThread number:3 finished task number:0 wait task number:6 任務 1 完成 任務 2 完成 任務 3 完成 任務 4 完成 任務 5 完成 任務 6 完成 WorkThread number:3 finished task number:6 wait task number:03、Java5提供的線程池
3.1 緩存線程池(newCachedThreadPool)
緩存線程池(newCachedThreadPool)可以創建任意個線程,每個任務過來后都會創建一個線程,用于任務少,或執行時間短的任務,例如我們創建十個任務,那么緩沖線程池將會創建十個線程來執行。如下代碼:
ExecutorService threadPool = Executors.newCachedThreadPool(); for(int i=1; i<=10; i++){final int taskId = i; threadPool.execute(new Runnable(){ public void run() { for(int i=1; i<=10; i++){ System.out.println(Thread.currentThread().getName() + " is looping of " + i + " the task is " + taskId); try { Thread.sleep(20); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); } System.out.println("add all of 10 task"); threadPool.shutdown();3.2 固定數量線程池(newFixedThreadPool)
固定數量線程池(newFixedThreadPool)允許我們創建固定線程數量的線程池,如果任務數大于線程池中線程的數量,那么任務將等待,如下代碼:
ExecutorService threadPool = Executors.newFixedThreadPool(3); for(int i=1; i<=10; i++){ final int taskId = i; threadPool.execute(new Runnable(){ public void run() { for(int i=1; i<=10; i++){ System.out.println(Thread.currentThread().getName() + " is looping of " + i + " the task is " + taskId); try { Thread.sleep(20); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); } System.out.println("add all of 10 task"); threadPool.shutdown();3.4 單一線程池(newSingleThreadExecutor)
如何實現線程掛掉后重新啟動?創建單一的線程池。
newSingleThreadExecutor(),這樣線程池中只會有一個線程工作,當線程失敗后會重新創建一個線程將失敗的線程替換掉。
3.5 定時器線程池(scheduleAtFixedRate)
定時器線程池(scheduleAtFixedRate)與定時器很類似,可以指定線程池中線程在多長時間后執行,以及每個多長時間執行一次,代碼如下,可以模擬讓炸彈在6s后爆炸,并且每個2s炸一次:
Executors.newScheduledThreadPool(3).scheduleAtFixedRate( // .schedule( new Runnable(){ public void run() { System.out.println("boming"); } }, 6, 2, TimeUnit.SECONDS); }總結
以上是生活随笔為你收集整理的【Java线程】线程池的原理和实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【定时任务】Quartz用法详解
- 下一篇: 【定时任务】JDK java.util.