Java 線程池的原理與實現

本博客不再更新,更多精彩內容請訪問我的獨立博客


最近在學習線程池、內存控制等關於提高程序運行性能方面的編程技術,在網上看到有一哥們寫得不錯,故和大家一起分享。


[分享]Java 線程池的原理與實現


這幾天主要是狂看源程序,在彌補了一些以前知識空白的同時,也學會了不少新的知識(比如 NIO),或者稱爲新技術吧。
線程池就是其中之一,一提到線程,我們會想到以前《操作系統》的生產者與消費者,信號量,同步控制等等。
一提到池,我們會想到數據庫連接池,但是線程池又如何呢?


建議:在閱讀本文前,先理一理同步的知識,特別是syncronized同步關鍵字的用法。
關於我對同步的認識,要緣於大三年的一本書,書名好像是 Java 實戰,這本書寫得實在太妙了,真正的從理論到實踐,從截圖分析到.class字節碼分析。哇,我想市場上很難買到這麼精緻的書了。作爲一個Java愛好者,我覺得絕對值得一讀。
我對此書印象最深之一的就是:equal()方法,由淺入深,經典!
還有就是同步了,其中提到了我的幾個編程誤區,以前如何使用同步提高性能等等,通過學習,使我對同步的認識進一步加深。


簡單介紹

    創建線程有兩種方式:繼承Thread或實現Runnable。Thread實現了Runnable接口,提供了一個空的run()方法,所以不論是繼承Thread還是實現Runnable,都要有自己的run()方法。
    一個線程創建後就存在,調用start()方法就開始運行(執行run()方法),調用wait進入等待或調用sleep進入休眠期,順利運行完畢或休眠被中斷或運行過程中出現異常而退出。

wait和sleep比較:
      sleep方法有:sleep(long millis),sleep(long millis, long nanos),調用sleep方法後,當前線程進入休眠期,暫停執行,但該線程繼續擁有監視資源的所有權。到達休眠時間後線程將繼續執行,直到完成。若在休眠期另一線程中斷該線程,則該線程退出。
      wait方法有:wait(),wait(long timeout),wait(long timeout, long nanos),調用wait方法後,該線程放棄監視資源的所有權進入等待狀態;
      wait():等待有其它的線程調用notify()或notifyAll()進入調度狀態,與其它線程共同爭奪監視。wait()相當於wait(0),wait(0, 0)。
      wait(long timeout):當其它線程調用notify()或notifyAll(),或時間到達timeout亳秒,或有其它某線程中斷該線程,則該線程進入調度狀態。
      wait(long timeout, long nanos):相當於wait(1000000*timeout + nanos),只不過時間單位爲納秒。



線程池:
    多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閒置時間,增加處理器單元的吞吐能力。
    
    假設一個服務器完成一項任務所需時間爲: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而在處理請求時浪費時間,從而提高效率。


/** 線程池類,工作線程作爲其內部類 **/

package org.ymcn.util;

import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;

/**
* 線程池
* 創建線程池,銷燬線程池,添加新任務
* 
* @author obullxl
*/
public final class ThreadPool {
    private static Logger logger = Logger.getLogger(ThreadPool.class);
    private static Logger taskLogger = Logger.getLogger("TaskLogger");

    private static boolean debug = taskLogger.isDebugEnabled();
    // private static boolean debug = taskLogger.isInfoEnabled();
    /* 單例 */
    private static ThreadPool instance = ThreadPool.getInstance();

    public static final int SYSTEM_BUSY_TASK_COUNT = 150;
    /* 默認池中線程數 */
    public static int worker_num = 5;
    /* 已經處理的任務數 */
    private static int taskCounter = 0;

    public static boolean systemIsBusy = false;

    private static List<Task> taskQueue = Collections
            .synchronizedList(new LinkedList<Task>());
    /* 池中的所有線程 */
    public PoolWorker[] workers;

    private ThreadPool() {
        workers = new PoolWorker[5];
        for (int i = 0; i < workers.length; i++) {
            workers[i] = new PoolWorker(i);
        }
    }

    private ThreadPool(int pool_worker_num) {
        worker_num = pool_worker_num;
        workers = new PoolWorker[worker_num];
        for (int i = 0; i < workers.length; i++) {
            workers[i] = new PoolWorker(i);
        }
    }

    public static synchronized ThreadPool getInstance() {
        if (instance == null)
            return new ThreadPool();
        return instance;
    }
    /**
    * 增加新的任務
    * 每增加一個新任務,都要喚醒任務隊列
    * @param newTask
    */
    public void addTask(Task newTask) {
        synchronized (taskQueue) {
            newTask.setTaskId(++taskCounter);
            newTask.setSubmitTime(new Date());
            taskQueue.add(newTask);
            /* 喚醒隊列, 開始執行 */
            taskQueue.notifyAll();
        }
        logger.info("Submit Task<" + newTask.getTaskId() + ">: "
                + newTask.info());
    }
    /**
    * 批量增加新任務
    * @param taskes
    */
    public void batchAddTask(Task[] taskes) {
        if (taskes == null || taskes.length == 0) {
            return;
        }
        synchronized (taskQueue) {
            for (int i = 0; i < taskes.length; i++) {
                if (taskes[i] == null) {
                    continue;
                }
                taskes[i].setTaskId(++taskCounter);
                taskes[i].setSubmitTime(new Date());
                taskQueue.add(taskes[i]);
            }
            /* 喚醒隊列, 開始執行 */
            taskQueue.notifyAll();
        }
        for (int i = 0; i < taskes.length; i++) {
            if (taskes[i] == null) {
                continue;
            }
            logger.info("Submit Task<" + taskes[i].getTaskId() + ">: "
                    + taskes[i].info());
        }
    }
    /**
    * 線程池信息
    * @return
    */
    public String getInfo() {
        StringBuffer sb = new StringBuffer();
        sb.append("\nTask Queue Size:" + taskQueue.size());
        for (int i = 0; i < workers.length; i++) {
            sb.append("\nWorker " + i + " is "
                    + ((workers[i].isWaiting()) ? "Waiting." : "Running."));
        }
        return sb.toString();
    }
    /**
    * 銷燬線程池
    */
    public synchronized void destroy() {
        for (int i = 0; i < worker_num; i++) {
            workers[i].stopWorker();
            workers[i] = null;
        }
        taskQueue.clear();
    }

    /**
    * 池中工作線程
    * 
    * @author obullxl
    */
    private class PoolWorker extends Thread {
        private int index = -1;
        /* 該工作線程是否有效 */
        private boolean isRunning = true;
        /* 該工作線程是否可以執行新任務 */
        private boolean isWaiting = true;

        public PoolWorker(int index) {
            this.index = index;
            start();
        }

        public void stopWorker() {
            this.isRunning = false;
        }

        public boolean isWaiting() {
            return this.isWaiting;
        }
        /**
        * 循環執行任務
        * 這也許是線程池的關鍵所在
        */
        public void run() {
            while (isRunning) {
                Task r = null;
                synchronized (taskQueue) {
                    while (taskQueue.isEmpty()) {
                        try {
                            /* 任務隊列爲空,則等待有新任務加入從而被喚醒 */
                            taskQueue.wait(20);
                        } catch (InterruptedException ie) {
                            logger.error(ie);
                        }
                    }
                    /* 取出任務執行 */
                    r = (Task) taskQueue.remove(0);
                }
                if (r != null) {
                    isWaiting = false;
                    try {
                        if (debug) {
                            r.setBeginExceuteTime(new Date());
                            taskLogger.debug("Worker<" + index
                                    + "> start execute Task<" + r.getTaskId() + ">");
                            if (r.getBeginExceuteTime().getTime()
                                    - r.getSubmitTime().getTime() > 1000)
                                taskLogger.debug("longer waiting time. "
                                        + r.info() + ",<" + index + ">,time:"
                                        + (r.getFinishTime().getTime() - r
                                                .getBeginExceuteTime().getTime()));
                        }
                        /* 該任務是否需要立即執行 */
                        if (r.needExecuteImmediate()) {
                            new Thread(r).start();
                        } else {
                            r.run();
                        }
                        if (debug) {
                            r.setFinishTime(new Date());
                            taskLogger.debug("Worker<" + index
                                    + "> finish task<" + r.getTaskId() + ">");
                            if (r.getFinishTime().getTime()
                                    - r.getBeginExceuteTime().getTime() > 1000)
                                taskLogger.debug("longer execution time. "
                                        + r.info() + ",<" + index + ">,time:"
                                        + (r.getFinishTime().getTime() - r
                                                .getBeginExceuteTime().getTime()));
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        logger.error(e);
                    }
                    isWaiting = true;
                    r = null;
                }
            }
        }
    }
}

/** 任務接口類 **/

package org.ymcn.util;

import java.util.Date;

/**
* 所有任務接口
* 其他任務必須繼承訪類
* 
* @author obullxl
*/
public abstract class Task implements Runnable {
    // private static Logger logger = Logger.getLogger(Task.class);
    /* 產生時間 */
    private Date generateTime = null;
    /* 提交執行時間 */
    private Date submitTime = null;
    /* 開始執行時間 */
    private Date beginExceuteTime = null;
    /* 執行完成時間 */
    private Date finishTime = null;

    private long taskId;

    public Task() {
        this.generateTime = new Date();
    }

    /**
    * 任務執行入口
    */
    public void run() {
        /**
        * 相關執行代碼
        * 
        * beginTransaction();
        * 
        * 執行過程中可能產生新的任務 subtask = taskCore();
        * 
        * commitTransaction();
        * 
        * 增加新產生的任務 ThreadPool.getInstance().batchAddTask(taskCore());
        */
    }

    /**
    * 所有任務的核心 所以特別的業務邏輯執行之處
    * 
    * @throws Exception
    */
    public abstract Task[] taskCore() throws Exception;

    /**
    * 是否用到數據庫
    * 
    * @return
    */
    protected abstract boolean useDb();

    /**
    * 是否需要立即執行
    * 
    * @return
    */
    protected abstract boolean needExecuteImmediate();

    /**
    * 任務信息
    * 
    * @return String
    */
    public abstract String info();

    public Date getGenerateTime() {
        return generateTime;
    }

    public Date getBeginExceuteTime() {
        return beginExceuteTime;
    }

    public void setBeginExceuteTime(Date beginExceuteTime) {
        this.beginExceuteTime = beginExceuteTime;
    }

    public Date getFinishTime() {
        return finishTime;
    }

    public void setFinishTime(Date finishTime) {
        this.finishTime = finishTime;
    }

    public Date getSubmitTime() {
        return submitTime;
    }

    public void setSubmitTime(Date submitTime) {
        this.submitTime = submitTime;
    }

    public long getTaskId() {
        return taskId;
    }

    public void setTaskId(long taskId) {
        this.taskId = taskId;
    }

}



關於高級線程池的探討

簡單線程池存在一些問題,比如如果有大量的客戶要求服務器爲其服務,但由於線程池的工作線程是有限的,服務器只能爲部分客戶服務,其它客戶提交的任務,只能在任務隊列中等待處理。一些系統設計人員可能會不滿這種狀況,因爲他們對服務器程序的響應時間要求比較嚴格,所以在系統設計時可能會懷疑線程池技術的可行性,但是線程池有相應的解決方案。調整優化線程池尺寸是高級線程池要解決的一個問題。主要有下列解決方案:

方案一:動態增加工作線程

在一些高級線程池中一般提供一個可以動態改變的工作線程數目的功能,以適應突發性的請求。一旦請求變少了將逐步減少線程池中工作線程的數目。當然線程增加可以採用一種超前方式,即批量增加一批工作線程,而不是來一個請求才建立創建一個線程。批量創建是更加有效的方式。該方案還有應該限制線程池中工作線程數目的上限和下限。否則這種靈活的方式也就變成一種錯誤的方式或者災難,因爲頻繁的創建線程或者短時間內產生大量的線程將會背離使用線程池原始初衷--減少創建線程的次數。

舉例:Jini中的TaskManager,就是一個精巧線程池管理器,它是動態增加工作線程的。SQL Server採用單進程(Single Process)多線程(Multi-Thread)的系統結構,1024個數量的線程池,動態線程分配,理論上限32767。

方案二:優化工作線程數目

如果不想在線程池應用複雜的策略來保證工作線程數滿足應用的要求,你就要根據統計學的原理來統計客戶的請求數目,比如高峯時段平均一秒鐘內有多少任務要求處理,並根據系統的承受能力及客戶的忍受能力來平衡估計一個合理的線程池尺寸。線程池的尺寸確實很難確定,所以有時乾脆用經驗值。

舉例:在MTS中線程池的尺寸固定爲100。

方案三:一個服務器提供多個線程池

在一些複雜的系統結構會採用這個方案。這樣可以根據不同任務或者任務優先級來採用不同線程池處理。

舉例:COM+用到了多個線程池。

這三種方案各有優缺點。在不同應用中可能採用不同的方案或者乾脆組合這三種方案來解決實際問題。

線程池技術適用範圍及應注意的問題

下面是我總結的一些線程池應用範圍,可能是不全面的。

線程池的應用範圍:

  1. 需要大量的線程來完成任務,且完成任務的時間比較短。 WEB服務器完成網頁請求這樣的任務,使用線程池技術是非常合適的。因爲單個任務小,而任務數量巨大,你可以想象一個熱門網站的點擊次數。 但對於長時間的任務,比如一個Telnet連接請求,線程池的優點就不明顯了。因爲Telnet會話時間比線程的創建時間大多了。
  2. 對性能要求苛刻的應用,比如要求服務器迅速相應客戶請求。
  3. 接受突發性的大量請求,但不至於使服務器因此產生大量線程的應用。突發性大量客戶請求,在沒有線程池情況下,將產生大量線程,雖然理論上大部分操作系統線程數目最大值不是問題,短時間內產生大量線程可能使內存到達極限,並出現"OutOfMemory"的錯誤。

結束語

本文只是簡單介紹線程池技術。可以看出線程池技術對於服務器程序的性能改善是顯著的。線程池技術在服務器領域有着廣泛的應用前景。希望這項技術能夠應用到您的多線程服務程序中。


本帖轉自:http://www.ibm.com/developerworks/cn/java/l-threadPool/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章