JAVA多線程基礎:線程池的原理與使用

爲啥要用線程池呢?

大概就以下原因吧!

  • 線程的使用如果不加控制和管理,過多的線程會對系統的性能會產生不利的影響
  • 線程的創建和消耗會佔用很多時間
  • 大量的線程回收會給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());
		}
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章