徹底搞定線程池(2)-基於模型實現線程池

概述

在上一篇文章 徹底搞定線程池(1)-線程池模型的構建 中,我把線程池的主要的功能和流程給梳理了一下,並且在最後使用代碼實現了一個簡單的線程池。不過距離一個完整的線程池還需要多做一些東西。

本篇的代碼會在上一版代碼的基礎上進行修改。地址爲 github地址,其中steap1爲上一篇的代碼,steap2爲本篇代碼

這裏先把線程池的功能羅列出來,之後一點點的把功能實現。

線程池需要提供的接口

  1. 任務的提交
  2. 溫和的關閉線程池:把已提交的任務完成,但拒絕新任務提交
  3. 強制關閉:不再執行任務,並且嘗試停止正在運行的任務

線程池內部需要實現的功能

  1. 線程的創建和管理
  2. 任務隊列的管理
  3. 處理任務(不斷從隊列中獲取任務來處理)
  4. 線程的閒置時間限制(可以設定閒置一定時間後停止該線程)

線程池中特殊情況的處理

  1. 飽和策略:有界的任務隊列中,如果任務放滿的了該如何處理
  2. 線程中斷重啓:在執行任務時線程被中斷了,需要及時的新開一個線程來繼續運行

流程圖

根據線程的功能需求,將任務提交的流程畫出來:
在這裏插入圖片描述

在線程池中,線程的運行過程也整理一下:
在這裏插入圖片描述

代碼實現

這裏把一些關鍵的代碼實現拿出來講講,反正都是按照整理好的流程來寫的。詳細的源碼在這裏,看懂我這個代碼,說明對線程池有足夠的瞭解,這時候去看Java的ThreadPoolExecutor源碼或相關的設計,就不會有云裏霧裏的感覺。

線程池的狀態管理

線程池的狀態通過state 變量來控制,當結束時,改變state的值即可。其他線程在運行的時候時檢測狀態,做出處理。

    //線程池的狀態
    private volatile int state = STATE_RUNNING;

    //線程池的2種狀態
    private final static int STATE_RUNNING = -1;
    private final static int STATE_SHUTDOWN = 0;
    private final static int STATE_SHUTDOWN_NOW = 2;
    
    //工作線程集合
    private volatile Set<Worker> workers;

    /**
     * 停止線程池
     * 溫和的停止,拒絕提交新任務,但是已提交的任務會全部完成
     */
    public void shutdown() {
        this.state = STATE_SHUTDOWN;
    }

    /**
     * 強制停止線程池
     * 拒絕提交任務,剩下的任務不再執行,並嘗試中斷線程池
     */
    public void shutdownNow() {
          this.state = STATE_SHUTDOWN_NOW;
          for(Worker w:workers){
              try {
                  w.t.interrupt();
              }catch (Exception e){
                  e.printStackTrace();
              }
          }
    }

任務的提交

    /**
     * 任務提交接口
     */
    public void execute(Task task) {

        if (state > STATE_RUNNING)
            throw new RejectedExecutionException("線程池已關閉,禁止提交任務");

        if (workers.size() < poolSize) {
            addWorker(task);
        } else {
            this.queue.add(task);
        }
    }
    
      /**
     * 添加worker工作線程,並立即執行
     * 這裏做個雙重判定,防止併發時多創建了線程
     */
    private void addWorker(Task task) {
        mainLock.lock();
        try {
            if (workers.size() >= poolSize) {
                this.queue.add(task);
                return;
            }
            Worker w = new Worker(task);
            workers.add(w);
            w.t.start();
        } finally {
            mainLock.unlock();
        }
    }

線程工作流程、線程異常中斷處理、線程閒置處理

     /**
     * 工作線程實際運行任務的方法
     */
    void runWorker(Worker worker) {
        Task task = (Task) worker.task;
        boolean completedAbruptly = false;
        try {
            while (true) {
                //線程在這個循環中不斷的獲取任務來執行
                // getTask() 爲從隊列中獲取任務,如果爲null,表示該線程超過閒置時間限制,停止該線程
                if (task == null) {
                    task = getTask();
                }

                if (task == null) {
                    completedAbruptly = true;
                    break;
                }

                task.run();
                task = null;
            }
            completedAbruptly = true;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //線程中斷,做一些處理
            processWorkerExit(worker, completedAbruptly);
        }
    }

    /**
     * 線程中斷,重新開啓線程
     */
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        //如果是因爲線程池關閉導致線程中斷,不做任何處理
        if (completedAbruptly)
            return;

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        //判斷線程池是否關閉狀態
        if (state == STATE_SHUTDOWN)
            return;

        addWorker(null);
    }

    /**
     * 從隊列中獲取可用的線程
     */
    private Task getTask() throws InterruptedException {
        if (state == STATE_SHUTDOWN_NOW)
            return null;

        if (state == STATE_RUNNING && queue.size() <= 0) {
            return null;
        }

        //如果有閒置時間限制,使用poll方法
        //一定時間內還未獲得可用任務,返回null
        if (allowThreadTimeOut) {
            return queue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
        }
        //如果隊列中暫時沒有任務,則線程進入阻塞狀態,直到獲取到任務
        return queue.take();
    }

總結

關鍵的代碼已給出,建議看完整的源碼,克隆下來,在自己電腦跑一跑。

至此一個線程池就完成了,雖然比較簡陋,但重要的東西都有。代碼寫會簡單一些,這是讓大家能更好的理解線程池。完全理解這個代碼時,就可以看java的線程池ThreadPoolExecutor的源碼了。到時候看源碼應該不會費勁了。

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