創建自己的多線程池

創建自己的多線程池類

本文通過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

類圖

ExecutorExecutorServiceExecutors 三個類直接的關係:
這裏寫圖片描述

總結:

其實我們自己定義的線程池也是借鑑JDK自帶的線程池的。在創建一個線程池的時候,都有這幾個必要元素:線程池大小線程存活時間線程工廠工作隊列任務拒絕處理方法
在使用JDK的線程池中,我們只用到兩個類,一個是ExecutorsExecutorService 這個是接口。其實還有一個接口很重要,因爲它是execute()方法的申明類,也是ExecutorService 的父接口,就是 Executor 。這三個類各司其職:

類名 作用
Executor 線程池類,它的execute()方法用來執行Runnable類型的任務。
ExecutorService 是Executor的子接口,申明瞭一些管理線程池的方法。比如shutdow()
Executors Executors包含一些靜態方法,它主要負責創建各種類型的ExecutorService實例。

歡迎關注微信公衆號 在路上的coder 每天分享優秀的Java技術文章!
掃描二維碼關注:這裏寫圖片描述

發佈了96 篇原創文章 · 獲贊 109 · 訪問量 90萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章