關於執行緒池,那些你還不知道的事

阿豪聊乾貨發表於2017-09-25

一、背景

  最近在學習執行緒相關的知識,然後順理成章少不了學習執行緒池,剛開始在沒有深入的學習之前,感覺執行緒池是很神祕的東西,而且完全想不到怎麼才能實現一個自己的執行緒池,然後還能保證它的可用性,然後就一直琢磨,琢磨了一週才不多,也是網上各種查資料,終於明白了執行緒池的原理,也自己手寫一個執行緒池,來加深印象,那麼本文我們就來聊一聊關於執行緒池的知識,希望更多的猿友能看到,從此對執行緒池有一個清晰直觀的認識。

二、概念解析

1.什麼是執行緒池

  執行緒池的基本思想是一種物件池,在程式啟動時就開闢一塊記憶體空間,裡面存放了眾多(未死亡)的執行緒,池中執行緒執行排程由池管理器來處理。當有執行緒任務時,從池中取一個,執行完成後執行緒物件歸池,這樣可以避免反覆建立執行緒物件所帶來的效能開銷,節省了系統的資源。

2.使用執行緒池的好處

  合理的使用執行緒池可以重複利用已建立的執行緒,這樣就可以減少在建立執行緒和銷燬執行緒上花費的時間和資源。並且,執行緒池在某些情況下還能動態的調整工作執行緒的數量,以平衡資源消耗和工作效率。同時執行緒池還提供了對池中工作執行緒進行統一的管理的相關方法。這樣就相當於我們一次建立,就可以多次使用,大量的節省了系統頻繁的建立和銷燬執行緒所需要的資源。

3.執行緒池的主要元件

一個執行緒池包括以下四個基本組成部分:
(1)、執行緒池管理器(ThreadPool):用於建立並管理執行緒池,包括 建立執行緒池,銷燬執行緒池,新增新任務;
(2)、工作執行緒(WorkThread):執行緒池中執行緒,在沒有任務時處於等待狀態,可以迴圈的執行任務;
(3)、任務介面(Task):每個任務必須實現的介面,以供工作執行緒排程任務的執行,它主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀態等;
(4)、任務佇列(taskQueue):用於存放沒有處理的任務。提供一種緩衝機制。

4.JDK中執行緒池常用類UML類關係圖

三、手寫實現

我們知道了執行緒池的原理以及主要元件之後,就讓我們來手動實現一個自己的執行緒池,以加深理解和深入學習。

1.執行緒池介面類

package com.hafiz.proxy.threadPool;

import java.util.List;

/**
 * Desc:執行緒池介面類
 * Created by hafiz.zhang on 2017/9/19.
 */
public interface ThreadPool {

    // 執行單個執行緒任務
    void execute(Runnable task);

    // 執行多個任務
    void execute(Runnable[] tasks);

    // 執行多個任務
    void execute(List<Runnable> tasks);

    // 返回已經執行的任務個數
    int getExecuteTaskNumber();

    // 返回等待被處理的任務個數,佇列的長度
    int getWaitTaskNumber();

    // 返回正在工作的執行緒的個數
    int getWorkThreadNumber();

    // 關閉執行緒池
    void destroy();
}

2.執行緒池實現類ThreadPoolManager.java

package com.hafiz.proxy.threadPool;

import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Desc:執行緒池實現類
 * Created by hafiz.zhang on 2017/9/19.
 */
public class ThreadPoolManager implements ThreadPool {

    // 執行緒池中預設執行緒的個數為5
    private static Integer workerNum = 5;

    // 工作執行緒陣列
    WorkThread[] workThreads;

    // 正在執行的執行緒任務數量
    private static volatile Integer executeTaskNumber = 0;

    // 任務佇列, 作為一個緩衝
    private Queue<Runnable> taskQueue = new ConcurrentLinkedQueue<>();

    // 單例模式
    private static ThreadPoolManager threadPool;

    private AtomicLong threadNum = new AtomicLong();

    private ThreadPoolManager() {
        this(ThreadPoolManager.workerNum);
    }

    private ThreadPoolManager(int workerNum) {
        if (workerNum > 0) {
            ThreadPoolManager.workerNum = workerNum;
        }
        workThreads = new WorkThread[ThreadPoolManager.workerNum];
        for (int i = 0; i < ThreadPoolManager.workerNum; i++) {
            workThreads[i] = new WorkThread();
            Thread thread = new Thread(workThreads[i], "ThreadPool-worker-" + threadNum.incrementAndGet());
            thread.start();
            System.out.println("初始化執行緒總數:" + (i+1) + ",當前執行緒名稱是:ThreadPool-worker-" + threadNum);
        }
    }

    public static ThreadPool getThreadPool() {
        return getThreadPool(workerNum);
    }

    public static ThreadPool getThreadPool(int workerNum) {
        if (workerNum > 0) {
            ThreadPoolManager.workerNum = workerNum;
        }
        if (threadPool == null) {
            threadPool = new ThreadPoolManager(ThreadPoolManager.workerNum);
        }
        return threadPool;
    }


    @Override
    public void execute(Runnable task) {
        synchronized (taskQueue) {
            taskQueue.add(task);
            taskQueue.notifyAll();
        }
    }

    @Override
    public void execute(Runnable[] tasks) {
        execute(Arrays.asList(tasks));
    }

    @Override
    public void execute(List<Runnable> tasks) {
        synchronized (taskQueue) {
            for (Runnable task : tasks) {
                 taskQueue.add(task);
            }
            taskQueue.notifyAll();
        }
    }

    @Override
    public String toString() {
        return "ThreadPoolManager{" +
                "當前的工作執行緒數量=" + getWorkThreadNumber() +
                ", 已完成的任務數=" + getExecuteTaskNumber() +
                ", 等待任務數=" + getWaitTaskNumber() +
                '}';
    }

    @Override
    public int getExecuteTaskNumber() {
        return executeTaskNumber;
    }

    @Override
    public int getWaitTaskNumber() {
        return taskQueue.size();
    }

    @Override
    public int getWorkThreadNumber() {
        return workerNum;
    }

    @Override
    public void destroy() {
        while (!taskQueue.isEmpty()) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int i = 0; i < workThreads.length; i++) {
            workThreads[i].shutdown();
            workThreads[i] = null;
        }
        threadPool = null;
        taskQueue.clear();
    }

    private class WorkThread implements Runnable {
        // 執行緒是否可用
        private boolean isRunning = true;

        @Override
        public void run() {
            Runnable r = null;
            while (isRunning) {
                // 佇列同步機制,加鎖
                synchronized (taskQueue) {
                    while (isRunning && taskQueue.isEmpty()) {
                        try {
                            taskQueue.wait(20);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (!taskQueue.isEmpty()) {
                        r = taskQueue.poll();
                    }
                }
                if (r != null) {
                    r.run();
                }
                executeTaskNumber++ ;
                r = null;
            }
        }

        public void shutdown() {
            isRunning = false;
        }
    }
}

其中該類中包含內部類WorkThread,它用來包裝真正的執行緒類,給每一個執行緒一個是否可用的標誌,該執行緒工作室同步的從taskQueue中取出要執行的任務進行呼叫run方法來執行任務。

這個類中的getThreadPool方法中我們還使用到了懶漢式來實現單例,單例模式也是Java常用設計模式之一。

注意該類中的destroy方法的實現:我們是一直等到佇列中的所有的任務執行完畢,才真正的銷燬執行緒池,銷燬的過程中不要忘記將每一個執行緒物件置為null,並且清空任務佇列,這樣更利於java的垃圾回收。

3.自定義任務類Task.java

package com.hafiz.proxy.threadPool;

/**
 * Desc:自定義任務類
 * Created by hafiz.zhang on 2017/9/21.
 */
public class Task implements Runnable {

    private static volatile Integer i = 1;

    @Override
    public void run() {
        // 執行任務
        synchronized (i) {
            System.out.println("當前處理的執行緒是:" + Thread.currentThread().getName() + ",執行任務:" + (i++) + "完成");
        }
    }
}

4.執行緒池測試類

package com.hafiz.proxy.threadPool;

import java.util.ArrayList;
import java.util.List;

/**
 * Desc:執行緒池測試類
 * Created by hafiz.zhang on 2017/9/20.
 */
public class ThreadPoolTest {
    public static void main(String[] args) {
        ThreadPool t = ThreadPoolManager.getThreadPool(6);
        List<Runnable> tasks = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            tasks.add(new Task());
        }
        System.out.println(t);
        t.execute(tasks);
        // 所有的執行緒執行完成才destroy
        t.destroy();
        System.out.println(t);
    }
}

5.測試結果:(為了篇幅,只建立10個任務執行)

四、總結

  通過本文,我們弄明白執行緒池到底是怎麼工作,學習知識的過程中,我們就是要知其然知其所以然。這樣我們才能更好地駕馭它,才能更好地去理解和使用,也能更好地幫助我們觸類旁通,後面有機會我們接著來說資料庫連線池的原理及手寫實現。

相關文章