用java手寫一個簡單的線程池

在手寫簡單的線程池之前首先需要清楚為什麼要用線程池,線程池存在的意義是什麼?

我們用火影忍者裡面鳴人的多重影分身術來理解,鳴人的多重影分身術可以出現成千上萬的分身,每出現一個分身都需要消耗一定的查克拉,這時候給鳴人分配一個任務,籃子裡面有100個雞蛋需要鳴人吃完,假設鳴人的查克拉總量是100,這時候鳴人放出100個影分身開始吃雞蛋,最後雞蛋吃完了,但鳴人的查克拉也消耗完了,目的雖然達成了但是也消耗完了自身的查克拉,每個分身在吃完雞蛋後就自己消失了,但是每個雞蛋大小各異,每個分身吃的速度也各不相同,有快的也有慢的,那有沒有辦法既能將雞蛋全都吃完還讓鳴人有更多的查克拉呢?

用java手寫一個簡單的線程池

這時候聰明的佐助給鳴人出了個主意讓他只分身50個,讓吃完的繼續吃剩下的雞蛋,鳴人照著做了,查克拉量不僅還剩下50,雞蛋也同樣吃光了。

根據這個任務肯定會有其他忍者說那50個肯定沒有100個吃得快,這個情況肯定是存在的,因為畢竟人多嗎?可是綜合考慮你的查克拉量本身就不大為何要讓自己掛掉完成任務呢?

所以這就跟我們做系統一樣,任何系統不應該以高負荷佔用資源去完成自己的任務,這樣只會讓系統崩潰的更快,從鳴人就可以衍生到計算機裡面,查克拉相當於有限的資源,吃雞蛋相當於很多用戶的操作或者任務,分身相當於很多個線程在做這些任務,這是一個資源與時間的取捨問題,當然第一要務不能宕機,所以我們選擇時間,但是吃雞蛋的人少未必就吃得慢,吃的快的可以吃得更多這樣資源得以最大化利用從而實現任務完成,系統永存,在衍生到線程池就更容易理解了,線程池的目的就是在不創建新線程的情況下讓已經執行完任務的線程再去執行新的任務,從而達到資源的有效利用而不是無節制的浪費資源。

任何技術原理可以不知道怎麼用代碼寫出來,但是必須知道這個技術存在的目的和技術思想以及為什麼要出現這個技術,與其說學編程是學技術倒不如說是學思想。

一、編寫線程池代碼,線程池主線程代碼,測試代碼

<code>import java.util.List;
import java.util.Vector;

/**
 * @author xiaomianyang
 * @description
 * @date 2020-02-08 14:31
 */
public class ThreadPool {
    private static ThreadPool instance=null;

    private List idleThreads;

    private int threadCounter;

    private boolean isShutdown=false;

    private ThreadPool(){
        this.idleThreads = new Vector<>(5);
        threadCounter = 0;
    }

    /**
     * @description 獲取創建的線程數量
     * @author xiaomianyang
     * @date 2020-02-15 16:14
     * @param []
     * @return int
     */
    public int getCreatedThreadsCount(){
        return threadCounter;
    }

    /**
     * @description 初始化線程池
     * @author xiaomianyang
     * @date 2020-02-15 16:15
     * @param []
     * @return ThreadPool
     */
    public synchronized static ThreadPool getInstance(){
        if(instance==null){
            instance = new ThreadPool();
        }
        return instance;
    }

    /**
     * @description 重新洗牌 如果線程池不被關閉 將空閒線程添加到線程池中
     * @author xiaomianyang
     * @date 2020-02-15 16:16
     * @param [repoolingThread]
     * @return void
     */
    protected synchronized void repool(PThread repoolingThread){
        if(!isShutdown){
            idleThreads.add(repoolingThread);
        }else{
            repoolingThread.shutDown();
        }
    }

    /**
     * @description 關閉線程池 對所有空閒線程進行依次循環關閉
     * @author xiaomianyang
     * @date 2020-02-15 16:17
     * @param []
     * @return void
     */
    public synchronized void shutDown(){
        isShutdown=true;
        for(int threadIndex=0;threadIndex0){
            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();
        }
    }

    public static void main(String[] args) {
        // 採用傳統方式創建10萬個線程
        new Thread(()->{
            long start=System.currentTimeMillis();
            for(int i=0;i<100000> {
            long start=System.currentTimeMillis();
            for (int i = 0; i < 100000; i++) {
                ThreadPool.getInstance().start(new MyThread("testThreadPool" + Integer.toString(i)));
            }
            System.out.println("線程總數量:" + ThreadPool.getInstance().getCreatedThreadsCount());
            System.out.println("線程池創建花費時間:"+(System.currentTimeMillis() - start) / 1000 + "秒");
        }).start();
    }
}

/**
 * @description 一個永不關閉的線程用於執行任務
 * @author xiaomianyang
 * @date 2020-02-15 16:18
 * @param
 * @return
 */
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;
    }

    /**
     * @description 這裡相當於線程池中的主線程 在不執行關閉的情況下 一直運行子線程的任務
     * @author xiaomianyang
     * @date 2020-02-15 16:19
     * @param []
     * @return void
     */
    public void run(){
        while(!isShutdown){
            isIdle=false;
            if(target!=null){
                target.run();
            }
            isIdle = true;
            try{
                pool.repool(this);
                synchronized (this){
                    wait();
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            isIdle = false;
        }
    }

    /**
     * @description 目標線程切換
     * @author xiaomianyang
     * @date 2020-02-15 16:19
     * @param [newTarget]
     * @return void
     */
    public synchronized void setTarget(Runnable newTarget){
        target=newTarget;
        notifyAll();
    }

    /**
     * @description 關閉線程
     * @author xiaomianyang
     * @date 2020-02-15 16:19
     * @param []
     * @return void
     */
    public synchronized void shutDown(){
        isShutdown = true;
        notifyAll();
    }
}

class MyThread implements Runnable{
    protected String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try{
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}/<code>

二、線程池詳解

這裡的ThreadPool類就是一個線程池子,他的作用就是創建和管理線程池,MyThread就是線程要執行的任務,PThread就是主線程,這個線程在不調用關閉方法前是一直在運行的,它的作用就相當於鳴人去發現那些已經吃完雞蛋的再去吃新的雞蛋不讓他們偷懶,main方法就是用來測試傳統創建線程和利用線程池創建線程花費的時間比。

這裡的流程就是循環創建十萬個線程,線程池每創建一個線程就會執行,如果該線程執行完成則將該線程放入到空閒線程集合中,當空閒線程集合大於0的時候就不再創建新的線程而使用空閒線程執行當前任務,這樣就可以很好的控制系統資源開銷。

三、運行結果如下

通過結果可以看出使用線程池要比傳統方式時間減少了將近一半,這裡由於兩個方法一起執行的,利用線程池花費了21秒,如果將main方法中的第一個線程註釋掉,會發現只需要3秒鐘,同樣的方式將第二個下稱註釋掉,傳統的方式執行需要花費19秒,這個差距可想而知,可見線程池的好處和優勢。

<code>線程總數量:33600
線程池創建花費時間:21秒
不用線程池創建花費時間:43秒/<code>

四、使用線程池的好處

使用線程池可以減少線程創建和銷燬對於資源的開銷,每次創建線程和銷燬線程都要花費時間和資源,而且CPU時間片在將近10萬個線程之間切換效率會非常低,所以我們採用線程池就是以最少的資源開銷完成更多的任務

五、線程池應用場景

那麼線程池可以用在什麼地方呢?一般的系統實際上都很難用到線程池,只有需要很多定時任務執行的系統才會用到,有些是系統本身任務有些是用戶觸發的任務,拿用戶觸發來說,比如用戶上傳照片到服務器,那麼有很多用戶都在上傳我們就需要很多的線程來處理,這時候可以利用一定數量的線程來處理,用線程池來管理,防止因為用戶量的劇增導致服務器線程數量增大導致服務器崩潰,這些線程可以供很多的用戶調用,還有很多其他的場景,比如用戶從服務器下載資源或者導出多個業務數據,我們就需要線程池在後端從各個業務模塊調取數據來處理,也就是在任務量不確定而且會很多的情況下使用線程池是最佳選擇。


分享到:


相關文章: