ThreadPool執行緒池

CDuck·發表於2020-10-07

為什麼要用執行緒池

例子:10年前單核CPU電腦,假的多執行緒,像馬戲團小丑玩多個球,CPU需要來回切換。現在是多核電腦,多個執行緒各自跑在獨立的CPU上,不用切換,效率高。

執行緒池的優勢: 執行緒池做的工作只要是控制執行的執行緒數量,處理過程中將任務放入佇列,然後線上程建立後啟動這些任務,如果執行緒數量超過了最大數量,超出數量的執行緒排隊等候,等其他執行緒執行完畢,再從佇列中取出任務來執行。

它的主要特點為:執行緒複用;控制最大併發數;管理執行緒。

第一: 降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的銷耗。
第二: 提高響應速度。當任務到達時,任務可以不需要等待執行緒建立就能立即執行。
第三: 提高執行緒的可管理性。執行緒是稀缺資源,如果無限制的建立,不僅會銷耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一的分配,調優和監控。

執行緒池如何使用

架構說明:
Java中的執行緒池是通過Executor框架實現的,該框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor這幾個類。

編碼實現:

Executors.newFixedThreadPool(int)

執行長期任務效能好,建立一個執行緒池,
一池有N個固定的執行緒,有固定執行緒數的執行緒

newFixedThreadPool建立的執行緒池corePoolSize和maximumPoolSize值是相等的,它使用的是LinkedBlockingQueue


public static ExecutorService newFixedThreadPool (int nThreads){

            return new ThreadPoolExecutor(nThreads,
             nThreads,
              0L, 
              TimeUnit.MILLISECONDS,
               new LinkedBlockingQueue<Runnable>());
       
        }
        

Executors.newSingleThreadExecutor()

一個任務一個任務的執行,一池一執行緒

newSingleThreadExecutor 建立的執行緒池corePoolSize和maximumPoolSize值都是1,它使用的是LinkedBlockingQueue


public static ExecutorService newSingleThreadExecutor () {

     return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(
     1, 
     1, 
     0L, 
     TimeUnit.MILLISECONDS,
     new LinkedBlockingQueue<Runnable>()));
        
}

Executors.newCachedThreadPool()

執行很多短期非同步任務,執行緒池根據需要建立新執行緒,
但在先前構建的執行緒可用時將重用它們。可擴容,遇強則強

newCachedThreadPool建立的執行緒池將corePoolSize設定為0,將maximumPoolSize設定為Integer.MAX_VALUE,它使用的是SynchronousQueue,也就是說來了任務就建立執行緒執行,當執行緒空閒超過60秒,就銷燬執行緒。

    public static ExecutorService newCachedThreadPool () {
            return new ThreadPoolExecutor(
            0, 
            Integer.MAX_VALUE, 
            60L,
            TimeUnit.SECONDS, 
            new SynchronousQueue<Runnable>());

        }

執行緒池7大重要引數

部分原始碼:

    public ThreadPoolExecutor(int corePoolSize, 
                              int maximumPoolSize, 
                              long keepAliveTime,
                              TimeUnit unit, 
                              BlockingQueue<Runnable> workQueue, 
                              ThreadFactory threadFactory, 
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    } 

1、corePoolSize:執行緒池中的常駐核心執行緒數

2、maximumPoolSize:執行緒池中能夠容納同時
執行的最大執行緒數,此值必須大於等於1

3、keepAliveTime:多餘的空閒執行緒的存活時間
當前池中執行緒數量超過corePoolSize時,當空閒時間
達到keepAliveTime時,多餘執行緒會被銷燬直到
只剩下corePoolSize個執行緒為止

4、unit:keepAliveTime的單位

5、workQueue:任務佇列,被提交但尚未被執行的任務

6、threadFactory:表示生成執行緒池中工作執行緒的執行緒工廠,
用於建立執行緒,一般預設的即可

7、handler:拒絕策略,表示當佇列滿了,並且工作執行緒大於
等於執行緒池的最大執行緒數(maximumPoolSize)時如何來拒絕
請求執行的runnable的策略

執行緒池底層工作原理

1、在建立了執行緒池後,開始等待請求。
2、當呼叫execute()方法新增一個請求任務時,執行緒池會做出如下判斷:
       2.1如果正在執行的執行緒數量小於corePoolSize,那麼馬上建立執行緒執行這個任務;
        2.2如果正在執行的執行緒數量大於或等於corePoolSize,那麼將這個任務放入佇列;
        2.3如果這個時候佇列滿了且正在執行的執行緒數量還小於maximumPoolSize,那麼還是要建立非核心執行緒立刻執行這個任務;
       2.4如果佇列滿了且正在執行的執行緒數量大於或等於maximumPoolSize,那麼執行緒池會啟動飽和拒絕策略來執行。
3、當一個執行緒完成任務時,它會從佇列中取下一個任務來執行。
4、當一個執行緒無事可做超過一定的時間(keepAliveTime)時,執行緒會判斷: 如果當前執行的執行緒數大於corePoolSize,那麼這個執行緒就被停掉。 所以執行緒池的所有任務完成後,它最終會收縮到corePoolSize的大小。

圖解示例:
在這裡插入圖片描述

執行緒池用哪個?生產中如設定合理引數

執行緒池的拒絕策略

是什麼
等待佇列已經排滿了,再也塞不下新任務了同時,執行緒池中的max執行緒也達到了,無法繼續為新任務服務。這個是時候我們就需要拒絕策略機制合理的處理這個問題。

JDK內建的拒絕策略

AbortPolicy(預設)CallerRunsPolicyDiscardOldestPolicyDiscardPolicy
直接丟擲RejectedExecutionException異常阻止系統正常執行“呼叫者執行”一種調節機制,該策略既不會拋棄任務,也不會丟擲異常,而是將某些任務回退到呼叫者,從而降低新任務的流量。拋棄佇列中等待最久的任務,然後把當前任務加人佇列中嘗試再次提交當前任務。該策略默默地丟棄無法處理的任務,不予任何處理也不丟擲異常。如果允許任務丟失,這是最好的一種策略。

以上內建拒絕策略均實現了RejectedExecutionHandle介面

在工作中單一的/固定數的/可變的三種建立執行緒池的方法哪個用的多?超級大坑

答案是一個都不用,我們工作中只能使用自定義的

Executors中JDK已經給你提供了,為什麼不用?

在這裡插入圖片描述
如何自定義 見下面的程式碼

程式碼

package cduck.cn;

import java.util.concurrent.*;

public class MyThreadPoolDemo {

    public static void main(String[] args) {
        //電腦CPU數量
        System.out.println(Runtime.getRuntime().availableProcessors());



        //一池5個工作執行緒。類似一個銀行有5個受理視窗
//        ExecutorService  threadPool= Executors.newFixedThreadPool(5);

//        一池1個工作執行緒,類似一個銀行有1個受理視窗
//        ExecutorService  threadPool= Executors.newSingleThreadExecutor();


        //一池N個工作執行緒,類似一個銀行N個視窗--自動擴容
//        ExecutorService  threadPool= Executors.newCachedThreadPool();


//自定義的執行緒池
        ExecutorService  threadPool= new ThreadPoolExecutor(
                2,
                5,
                2L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );




        try{
//模擬銀行有10個顧客來辦理銀行業務,目前池子裡有5個工作人員提供服務
                    for (int i=0;i<20;i++){
                        threadPool.execute(()->{
                            System.out.println(Thread.currentThread().getName()+"\t 辦理業務");
                        });
//                    TimeUnit.SECONDS.sleep(1);
                    }
                }catch (Exception e){
                      e.printStackTrace();
                }finally {
                        threadPool.shutdown();
                }

    }
}


相關文章