爲啥要用線程池呢?
大概就以下原因吧!
- 線程的使用如果不加控制和管理,過多的線程會對系統的性能會產生不利的影響
- 線程的創建和消耗會佔用很多時間
- 大量的線程回收會給GC帶來很大的壓力,並且會延遲GC停頓時間
模擬線程池
這裏只是簡單的模擬線程池,讓大家瞭解原理,真正的線程池比這個麻煩很多……
其中,我們只需要2個類
- ThreadPool:這個就是線程池,他的主要作用的管理線程,有線程就提供,沒有線程了就創建再提供,用完了就回收
- PThread:線程池都有了,那這個是幹什麼的呢?一般我們創建的線程開始後,都會自動的銷燬,所以這個類相當於一個包裝類(其實不能算包裝類),他讓我們的線程運行完後,不是銷燬,而是回收到Threadpool中去
下面我們仔細的看看這2個類吧!
PThread
爲了方便理解!先說這個類乾的事情吧!他將我們創建的線程中run的方法中的“代碼”提取出來,放到自己的run方法中,當提取的代碼運行完畢後,他自己就處於等待狀態(wait),並將自己放入ThreadPool類中,等待下次被調用
所以:
- 這個類繼承了Thread類,他是在線程中真正運行的類
- 他需要傳入一個線程才能運行
- 他得到傳入的線程後,run方法會提取被調用的被傳入的線程中的run方法中的代碼
- 當一個線程中的方法運行完畢後,他會讓自己停止,並將自己放入ThreadPool中去
- 當有新的“線程”被傳進來後,PThread會被再次喚醒
ThreadPool
理解了PThread類,這個類相對比較簡單了,他負責管理PThread類
當外部需要線程時
- 如果沒有空閒的PThread,他就創建新的PThread類,並將需要運行的線程傳入到PTread中,並讓“PThread”類運行
- 如果有空閒的PThread,就直接調用,同上面一樣
示例代碼
- PThread類
public class PThread extends Thread
{
//線程池
private ThreadPool pool;
//任務
private Runnable target;
private boolean isShutDown = false;
private boolean isIdle = false;
public PThread(Runnable target, String name, ThreadPool pool)
{
super(name);
this.pool = pool;
this.target = target;
}
public Runnable getTarget()
{
return target;
}
public boolean isIdle()
{
return isIdle;
}
public void run()
{
while (!isShutDown)
{
isIdle = false;
if (target != null)
{
// 運行任務
target.run();
}
//任務結束了
isIdle = true;
try
{
//該任務結束後,不關閉線程,而是放入線程池空閒隊列
pool.repool(this);
synchronized (this)
{
//線程空閒,等待新的任務到來
wait();
}
}
catch (InterruptedException ie)
{
}
isIdle = false;
}
}
public synchronized void setTarget(java.lang.Runnable newTarget)
{
target = newTarget;
//設置了任務之後,通知run方法,開始執行這個任務
notifyAll();
}
public synchronized void shutDown()
{
isShutDown = true;
notifyAll();
}
}
- ThreadPool類
public class ThreadPool
{
private static ThreadPool instance = null;
//空閒的線程隊列
private List<PThread> idleThreads;
//已有的線程總數
private int threadCounter;
private boolean isShutDown = false;
private ThreadPool()
{
this.idleThreads = new Vector(5);
threadCounter = 0;
}
public int getCreatedThreadsCount() {
return threadCounter;
}
//取得線程池的實例
public synchronized static ThreadPool getInstance() {
if (instance == null)
instance = new ThreadPool();
return instance;
}
//將線程放入池中
protected synchronized void repool(PThread repoolingThread)
{
if (!isShutDown)
{
idleThreads.add(repoolingThread);
}
else
{
repoolingThread.shutDown();//關閉線程
}
}
//停止池中所有線程
public synchronized void shutdown()
{
isShutDown = true;
for (int threadIndex = 0; threadIndex < idleThreads.size(); threadIndex++)
{
PThread idleThread = (PThread) idleThreads.get(threadIndex);
idleThread.shutDown();
}
}
//執行任務
public synchronized void start(Runnable target)
{
PThread thread = null;
//如果有空閒線程,則直接使用
if (idleThreads.size() > 0)
{
int lastIndex = idleThreads.size() - 1;
thread = (PThread) idleThreads.get(lastIndex);
idleThreads.remove(lastIndex);
//立即執行這個任務
thread.setTarget(target);
}
//沒有空閒線程,則創建新線程
else
{
threadCounter++;
// 創建新線程,
thread = new PThread(target, "PThread #" + threadCounter, this);
//啓動這個線程
thread.start();
}
}
}
- 調用方式
for(int i=0;i<1000;i++){
ThreadPool.getInstance().start(new MyThread("testThreadPool"+Integer.toString(i)));
}
調用需要同時開很多線程纔有效果,否則還不如單線程來的快
理解了線程池原理,我們可以來看一下真正線程池的用法
ExecutorService
下面是真實的線程池的調用方法,使用的newCachedThreadPool,下面有具體介紹
ExecutorService exe=Executors.newCachedThreadPool();
for(int i=0;i<1000;i++){
exe.execute(new MyThread("testJDKThreadPool"+Integer.toString(i)));
}
各種線程池的功能
這裏使用的工廠方法(一種設計模式),可以創建各種線程池
- newCachedThreadPool : 上面使用的線程池,和我們模擬的類似,線程的數量隨着需要不斷的變化
- newFixedThreadPool : 線程的數量是固定的,如果不夠就等待
- newSingleThreadExecutor:只有一個線程,不夠就排隊等待
- newSingleThreadScheduledExecutor:返回一個線程的線程池ScheduledExecutorService,可以延時或者週期執行
- newSingleThreadScheduledExecutor:同上面一樣,不同在於可以指定線程數量
線程的等待策略
這個是當線程滿了,新線程又來了,對新線程的處理策略有哪些?
- 直接提交策略:如果線程數量滿了,就直接拒絕,沒有線程等待
- 有界的任務隊列:可以設置最大等待的線程數量,如果大於最大的線程等待數量,之後的線程就直接拒絕
- 無界的任務隊列:不會拒絕線程的等待數量,直到系統資源耗盡
- 優先任務隊列:任務優先級高的線程先執行,任務數量與無界的一樣,傳入的線程必須實現Comparable接口
拒絕策略
根據上面知道,當線程滿了,我們拒絕線程執行的方式有哪些呢?
- Abortpolicy策略:直接拋出異常,阻止系統正常工作。
- CallerRunsPolicy策略:只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務(返回主線程,誰調用誰執行,也就是不拋棄任務)。
- DiscardOledestPolicy策略:將即將執行的請求丟棄掉,並嘗試再次提交當前任務。
- DiscardPolicy策略:默默丟棄無法處理的任務,不予任何處理。
- 自己實現RejectedExecutionHandler接口,實現拒絕策略
線程池最優大小值
線程池過大或者過小都不利於系統的運行,下面方法是獲得最佳線程池大小的方法:
Runtime.getRuntime().availableProcessors()
線程追蹤
在實際應用中,可以對線程池運行狀態進行跟蹤,輸入一些有用的調試信息,以幫助系統故障診斷,你只需要重寫ThreadPoolExecutor方法
public class MyThreadPoolExecutor extends ThreadPoolExecutor{
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("beforeExecute MyThread Name:"+((MyThread)r).getName()+" TID:"+t.getId());
}
protected void afterExecute(Runnable r, Throwable t) {
System.out.println("afterExecute TID:"+Thread.currentThread().getId());
System.out.println("afterExecute PoolSize:"+this.getPoolSize());
}
}