利用等待-通知模式實現簡單的ThreadPool

寫在前面

??說實話,我個人是不太喜歡寫博客或技術文章的。一來是因為個人水平有限,怕寫出來的東西錯誤百出,誤人子弟;二來在學習的過程中,如果想著寫博客輸出wiki等,會嚴重影響思路(我個人是這么認為的),所以幾次想嘗試寫寫學習筆記啥的都沒能走出第一步。前幾天,因為某些原因給我一些觸動,讓我覺得還是要向某人學習,該輸出的還是要輸出。

??本文主要是我在前段時間學習Java多線程相關知識時,總結的一些個人實操,代碼本機測試OK(PC : MacBook Pro - Mac OS 10.11.6 , JDK : JDK 1.8.0_40),如發現BUG之類,歡迎各位指正。除此之外,說明一下,本文部分代碼和內容參考自《Java并發編程的藝術》與《Java并發編程實戰》,如有雷同,就是引用。

引言

??相信大多數程序員在學習或工作中都聽過或使用過線程池這一簡單高效的并發組件,尤其在一些對響應時間要求非常嚴苛的場景,使用線程池能讓我們高效方便的的寫出并發代碼。但是對于相當一部分人來說,對于線程池的認識也僅僅停留在用過甚至是聽過的階段,并沒有真正的去了解它的工作原理和實現方式。借著最近學習《Java并發編程的藝術》契機,我看了JDK8中幾種線程池的實現和它們對應的特性,再結合書中的案例,實現了一個簡單的線程池。本線程池并沒有過多的考慮各種各樣的應用場景,只是為了在自己學習的基礎上,探究并實踐了線程池實現的原理和機制,雖說代碼不多,但是其用到的思想還是很值得我們在編寫代碼中學習的,個人認為這種:**獲得鎖 → 條件不滿足 → 等待 → 阻塞 → 釋放鎖 → 被通知 → 獲得鎖 → 阻塞返回并執行 → 釋放鎖 **的模式能實現很多并發,異步,消費者生產者的問題。所以代碼不多,思想很妙。下面是示例代碼:

1、線程池接口

??示例代碼:

/**
 * Created by luxiaohong on 17/3/7.
 */
public interface IThreadPool<Job extends Runnable> {
    int MAX_THREAD_COUNT = 1024;  //線程池允許最大的活動線程數
    int DEFAULT_THREAD_COUNT = 10;  //線程池默認線程數
    int MIN_THREAD_COUNT = 1;  //線程池最小線程數
    void submit(Job job);  //提交任務至線程池執行
    void shutdown(int count);  //關閉一定數量的線程數
    void shutdownAll();  //關閉所有數量的線程
} ```
###2、線程池實現類
&emsp;&emsp;示例代碼:

/**

  • Created by luxiaohong on 17/3/7.
    */
    public class ThreadPool<Job extends Runnable> implements IThreadPool<Job>{

    private volatile boolean isShutdown = false; //標識線程池是否被關閉

    private int threadCount = DEFAULT_THREAD_COUNT;

    private int threadNum = 0; //標識線程的編號

    private final LinkedList<Job> jobs = new LinkedList<>(); //用LinkedList來組織待處理任務列表

    private final List<Worker> workers = Collections.synchronizedList(new ArrayList<>()); //用List組織工作者線程

    public ThreadPool(){
    this(DEFAULT_THREAD_COUNT);
    }

    public ThreadPool(int initSize){
    if (initSize < MIN_THREAD_COUNT) {
    throw new IllegalArgumentException("initSize is negative");
    }
    if (initSize > MAX_THREAD_COUNT) {
    initSize = MAX_THREAD_COUNT;
    }
    for (int i = 0; i < initSize; i++) { //初始化工作者線程,并啟動線程
    Worker worker = new Worker();
    Thread t = new Thread(worker, "thread-"+threadNum++);
    workers.add(worker);
    t.start();

     }
    

    }

class Worker implements Runnable{
    //使用volatile修飾,保證其他線程修改,本線程立即可見
    private volatile boolean isAlive = true;

    @Override
    public void run(){
        while (isAlive) {  //首先判斷線程是否存活
            Job job = null;
            synchronized (jobs) {  //對任務集合進行加鎖
                while (isAlive && jobs.isEmpty()) {  //當線程存活且任務隊列為空,本工作線程阻塞等待
                    try {
                        jobs.wait();  //阻塞等待
                    } catch (InterruptedException e) {
                        //當拋出InterruptedException時,終端標志位會被重置,需要重新設置
                        Thread.currentThread().interrupt();
                        return;
                    }
                }
                //判斷線程從阻塞返回的條件,如果jobs不為空,則執行
                if (!jobs.isEmpty()) {
                    job = jobs.removeFirst();
                }
            }
             //此處需判斷job是否為空,因為跳出循環可能是由于線程被殺死
            if (job != null) {
                job.run();
            }
        }
    }

    //暫停worker
    public void shutdown(){
        this.isAlive = false;
    }
}

@Override
public void submit(Job job) {
    if (job == null) {
        throw new NullPointerException("job is null.");
    }
    if (isShutdown) {
        throw new IllegalStateException("threadpool is shutdown.");
    }
    synchronized (jobs) {  //當提交一個任務后,先將任務隊列加鎖在將任務加入隊尾,最后通知工作線程并釋放鎖
        jobs.addLast(job);
        jobs.notify();
    }
}

@Override
public void shutdownAll() {
    if (isShutdown) {
        throw new IllegalStateException("threadpool is shutdown.");
    }
    for (Worker worker : workers) {
        worker.shutdown();
    }
    this.isShutdown = true;

}

@Override
public void shutdown(int count) {
    synchronized (workers) {
        if (count > threadCount) {
            throw new IllegalArgumentException("shutdown thread count gt current thread count.");
        }
        if (count == threadCount) {
            //關閉所有線程
            shutdownAll();
            return;
        }
        for (int i = threadCount - 1; i > threadCount - count; i--) {
            workers.get(i).shutdown();
        }
        threadCount -= count;
    }
}

}

###3、測試線程池主程序
&emsp;&emsp;示例代碼:

/**

  • Created by luxiaohong on 17/3/8.
    */
    public class ThreadPoolTest {
    public static void main(String[] args) {
    ThreadPool threadPool = new ThreadPool();
    for (int i =0 ; i < 1000 ; i++) {
    threadPool.submit(() -> {
    try {
    Thread.sleep(500);
    System.out.println(Thread.currentThread().getName()+"執行完畢");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    });
    }
    //threadPool.shutdownAll();
    }
    }
###4、執行結果

![線程池測試主程序執行效果圖  MacBook Pro - Mac OS 10.11.6](http://upload-images.jianshu.io/upload_images/5343648-d164b1d40a5a583b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
###5、總結
&emsp;&emsp;此線程池實際包含兩個重要的組成部分。第一個是任務執行者池(Runnable),第二個是待執行任務池(Runnable)。線程池實現思想就是,當待執行任務池為空即沒有任務時,任務執行者池中的任務執行者(使用Thread包裝的Runable對象)會阻塞在待執行任務池這個條件上,一旦客戶端提交了一個任務,客戶端線程首先對待執行任務池加鎖,然后將任務安全的加入到待執行任務池中,最后通知任務執行者池中的某一個任務執行者,被選中通知的任務執行者將會從待執行任務池中獲取這個任務,然后執行下去。至此,一個任務從提交到執行結束的生命周期就這樣完成了。我們都知道在Java中要想新建一個線程,有兩種方式,一是實現Runnable接口,然后用Thread類包裝,start()調用執行;二是繼承Thread類,重寫run()方法,然后實例化一個對象,start()執行。之前了解到使用實現Runnable接口的方式有很多優點,通過線程池的實現也可以看出,這種方式對于一些復雜任務,更高級的并發組件實現起來,具有很好的解耦性。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容

  • 譯序 本指南根據 Jakob Jenkov 最新博客翻譯,請隨時關注博客更新:http://tutorials.j...
    高廣超閱讀 5,153評論 1 68
  • 前段時間遇到這樣一個問題,有人問微信朋友圈的上傳圖片的功能怎么做才能讓用戶的等待時間較短,比如說一下上傳9張圖片,...
    加油碼農閱讀 1,215評論 0 2
  • 作者: 一字馬胡 轉載標志 【2017-11-01】 更新日志 日期更新內容備注2017-11-01新建文章V1...
    一字馬胡閱讀 7,446評論 9 134
  • 作者:佐藤信夫譯者:肖書文版本:重慶大學出版社 2012年12月第1版第1次印刷來源:微盤下載的PDF 最近一段時...
    馬文Marvin閱讀 2,648評論 3 7
  • 夜里的樹 跟白天并不一樣 白天挺拔高大 夜里驚悚 樹葉被風吹過 沙沙沙 就像我面對你 一半熱火燒身 一半冰冷 嘴唇...
    又見一刀閱讀 165評論 0 1