2020最新Java線程池入門(超詳細)

1 爲什麼要使用線程池?

a.假如線程創建的時間是time1,線程執行的時間是time2,線程銷燬的時間呢是time3,往往time1+time3>time2,所以頻繁的創建線程,會消耗額外的時間
b.如果等到有任務來了,在去創建線程的話效率就會比較低,如不把線程放在某個地方,任務來了,直接把線程拿過來用比較好
c.線程池可以管理控制線程,線程是稀缺資源,如果不停地創建線程會消耗大量的系統資源,還會降低系統的穩定性。使用線程池,可以進行統一分配,方便調優和監控
d.線程可以提供隊列,存放緩衝等待執行的任務

2 效果對比

  • 下面我們來模擬一下 100個用戶同時訪問web端 執行一個操作

普通線程啓動方式

 /**
     * 普通處理線程的方法
     */
    @Test
    public void oldMethod() throws InterruptedException {
        //書寫一個循環  模擬100個用戶同時訪問
        for (int request = 0; request< 100;request ++){
            //開啓一個線程
            new Thread(()->{
                System.out.println("開始執行...");
                /**
                 * 睡10秒
                 */
                try {
                    Thread.sleep(1000L * 30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("結束執行...");
            }).start();
        }
        //爲了方便測試  我們讓主線程睡一會
        Thread.sleep(1000 * 1000);
    }

這時我們看下下面的圖片的,一共有100個線程在執行,如果要是10000個用戶來訪問該程序,系統不得直接給幹崩潰了!所以我們就得使用下面的線程池
在這裏插入圖片描述
利用線程池啓動線程方式

 @Test
    public void newMethod() throws InterruptedException {
        /**
         * 開啓一個線程池  默認線程是10個
         * 使用默認線程工廠
         * 拒絕策略爲CallerRunsPolicy策略,讓後面的線程先等待 
         */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 1000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());

            //書寫一個循環  模擬100個用戶同時訪問
            for (int request = 0; request< 100;request ++){
                //開啓一個線程
                threadPoolExecutor.execute(() ->{
                    System.out.println("開始執行...");
                    /**
                     * 睡10秒
                     */
                    try {
                        Thread.sleep(1000L * 30);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("結束執行...");
                });
            }

        //爲了方便測試  我們讓主線程睡一會
        Thread.sleep(1000 * 1000);
    }

我們可以看到這時候只有10個線程在跑,剩下的也都在等待該這是個線程跑完,纔會有新的線程執行
在這裏插入圖片描述

3 線程池簡介

  • 降低資源消耗
  • 提高響應速度
  • 提高線程的可管理性

4 簡單線程池的設計

簡單線程池設計思路
要有阻塞隊列丶執行器丶線程池子(存放線程)
在這裏插入圖片描述

  • 設計過程中要思考的問題
    • 初始創建多少線程?
    • 沒有可用線程了怎麼辦?
    • 緩衝數組要多長?
    • 緩衝數組滿了怎麼辦?

5 線程池的核心參數

  • corePoolSize 核心池大小
  • maximumPoolSize 池中允許的最大線程,這個參數表示了線程池中最多的線程數量
  • keepAliveTime 當線程大於corePoolSize時,終止前多餘的空閒線程等待新任務的最長時間
  • unit 上一個參數的時間單位
  • workQueue 存儲還沒來的及執行的任務
  • threadFactory 執行程序創建新線程時使用的工廠
  • handler 由於超出線程範圍和隊列容量而使執行被阻塞時使用的處理程序

6 線程池的處理流程

從下面這張圖我們可以看出:
1.提交任務
2.判斷線程數是否達到最大(是則進入判斷任務隊列)否就提交任務
3.判斷任務隊列是否已滿(是則進入判斷最大線程數)否就將任務加在任務隊列
4.判斷線程達到最大線程數(是則進入飽和策略)否則創建非核心線程執行任務
5.執行飽和策略,根據飽和策略的不同,程序作出不同的響應
在這裏插入圖片描述

7 線程池可選擇的阻塞隊列

  • 無界隊列(常用的爲無界的LinkedBlockingQueue)
    可以無限創建線程(有風險)
  • 有界隊列
    常用的有兩類,一類是遵循FIFO原則的隊列如ArrayBlockingQueue,另一類是優先級隊列如PriorityBlockingQueue。PriorityBlockingQueue中的優先級由任務的Comparator決定。
    使用有界隊列時隊列大小需和線程池大小互相配合,線程池較小有界隊列較大時可減少內存消耗,降低cpu使用率和上下文切換,但是可能會限制系統吞吐量。

在我們的修復方案中,選擇的就是這個類型的隊列,雖然會有部分任務被丟失,但是我們線上是排序日誌蒐集任務,所以對部分對丟失是可以容忍的。

  • 同步移交隊列
    如果不希望任務在隊列中等待而是希望將任務直接移交給工作線程,可使用SynchronousQueue作爲等待隊列。SynchronousQueue不是一個真正的隊列,而是一種線程之間移交的機制。要將一個元素放入SynchronousQueue中,必須有另一個線程正在等待接收這個元素。只有在使用無界線程池或者有飽和策略時才建議使用該隊列。

8 線程池可選擇的飽和策略

	AbortPolicy終止策略(默認)
	DiscardPolicy拋棄策略
	DiscardOldestPolicy拋棄舊任務策略
	CallerRunsPolicy調用者運行策略

9 線程池的執行示意圖

在這裏插入圖片描述

10 常用線程池

	newCachedThreadPool線程數量無限線程池
	newFixedThreadPool線程數量固定線程池
	newSingleThreadExecutor單一線程線程池

11 線程池的狀態

五種狀態

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