1. 程式人生 > 實用技巧 >多執行緒_執行緒池

多執行緒_執行緒池

1.執行緒池

1.1 執行緒狀態介紹

當執行緒被建立並啟動以後,它既不是一啟動就進入了執行狀態,也不是一直處於執行狀態。執行緒物件在不同的時期有不同的狀態。那麼Java中的執行緒存在哪幾種狀態呢?Java中的執行緒

狀態被定義在了java.lang.Thread.State列舉類中,State列舉類的原始碼如下:

public class Thread {
    
    public enum State {
    
        /* 新建 */
        NEW , 
​
        /* 可執行狀態 */
        RUNNABLE , 
​
        /* 阻塞狀態 
*/ BLOCKED , ​ /* 無限等待狀態 */ WAITING , ​ /* 計時等待 */ TIMED_WAITING , ​ /* 終止 */ TERMINATED; } // 獲取當前執行緒的狀態 public State getState() { return jdk.internal.misc.VM.toThreadState(threadStatus); } }

通過原始碼我們可以看到Java中的執行緒存在6種狀態,每種執行緒狀態的含義如下

執行緒狀態具體含義
NEW 一個尚未啟動的執行緒的狀態。也稱之為初始狀態、開始狀態。執行緒剛被建立,但是並未啟動。還沒呼叫start方法。MyThread t = new MyThread()只有執行緒象,沒有執行緒特徵。
RUNNABLE 當我們呼叫執行緒物件的start方法,那麼此時執行緒物件進入了RUNNABLE狀態。那麼此時才是真正的在JVM程序中建立了一個執行緒,執行緒一經啟動並不是立即得到執行,執行緒的執行與否要聽令與CPU的排程,那麼我們把這個中間狀態稱之為可執行狀態(RUNNABLE)也就是說它具備執行的資格,但是並沒有真正的執行起來而是在等待CPU的度。
BLOCKED
當一個執行緒試圖獲取一個物件鎖,而該物件鎖被其他的執行緒持有,則該執行緒進入Blocked狀態;當該執行緒持有鎖時,該執行緒將變成Runnable狀態。
WAITING 一個正在等待的執行緒的狀態。也稱之為等待狀態。造成執行緒等待的原因有兩種,分別是呼叫Object.wait()、join()方法。處於等待狀態的執行緒,正在等待其他執行緒去執行一個特定的操作。例如:因為wait()而等待的執行緒正在等待另一個執行緒去呼叫notify()或notifyAll();一個因為join()而等待的執行緒正在等待另一個執行緒結束。
TIMED_WAITING 一個在限定時間內等待的執行緒的狀態。也稱之為限時等待狀態。造成執行緒限時等待狀態的原因有三種,分別是:Thread.sleep(long),Object.wait(long)、join(long)。
TERMINATED 一個完全執行完成的執行緒的狀態。也稱之為終止狀態、結束狀態

各個狀態的轉換,如下圖所示:

1.2 執行緒池-基本原理

概述 :

提到池,大家應該能想到的就是水池。水池就是一個容器,在該容器中儲存了很多的水。那麼什麼是執行緒池呢?執行緒池也是可以看做成一個池子,在該池子中儲存很多個執行緒。

執行緒池存在的意義:

系統建立一個執行緒的成本是比較高的,因為它涉及到與作業系統互動,當程式中需要建立大量生存期很短暫的執行緒時,頻繁的建立和銷燬執行緒對系統的資源消耗有可能大於業務處理是對系

統資源的消耗,這樣就有點"捨本逐末"了。針對這一種情況,為了提高效能,我們就可以採用執行緒池。執行緒池在啟動的時,會建立大量空閒執行緒,當我們向執行緒池提交任務的時,執行緒池就

會啟動一個執行緒來執行該任務。等待任務執行完畢以後,執行緒並不會死亡,而是再次返回到執行緒池中稱為空閒狀態。等待下一次任務的執行。

執行緒池的設計思路 :

    1. 準備一個任務容器

    2. 一次性啟動多個(2個)消費者執行緒

    3. 剛開始任務容器是空的,所以執行緒都在wait

    4. 直到一個外部執行緒向這個任務容器中扔了一個"任務",就會有一個消費者執行緒被喚醒

    5. 這個消費者執行緒取出"任務",並且執行這個任務,執行完畢後,繼續等待下一次任務的到來

1.3 執行緒池-Executors預設執行緒池

概述 : JDK對執行緒池也進行了相關的實現,在真實企業開發中我們也很少去自定義執行緒池,而是使用JDK中自帶的執行緒池。

我們可以使用Executors中所提供的靜態方法來建立執行緒池

static ExecutorService newCachedThreadPool() 建立一個預設的執行緒池 static newFixedThreadPool(int nThreads) 建立一個指定最多執行緒數量的執行緒池

程式碼實現 :

package com.itheima.mythreadpool;
​
​
//static ExecutorService newCachedThreadPool()   建立一個預設的執行緒池
//static newFixedThreadPool(int nThreads)       建立一個指定最多執行緒數量的執行緒池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
​
        //1,建立一個預設的執行緒池物件.池子中預設是空的.預設最多可以容納int型別的最大值.
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Executors --- 可以幫助我們建立執行緒池物件
        //ExecutorService --- 可以幫助我們控制執行緒池
​
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "在執行了");
        });
​
        //Thread.sleep(2000);
​
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "在執行了");
        });
​
        executorService.shutdown();
    }
}
​

1.4 執行緒池-Executors建立指定上限的執行緒池

使用Executors中所提供的靜態方法來建立執行緒池

static ExecutorService newFixedThreadPool(int nThreads) : 建立一個指定最多執行緒數量的執行緒池

程式碼實現 :

package com.itheima.mythreadpool;
​
//static ExecutorService newFixedThreadPool(int nThreads)
//建立一個指定最多執行緒數量的執行緒池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
​
public class MyThreadPoolDemo2 {
    public static void main(String[] args) {
        //引數不是初始值而是最大值
        ExecutorService executorService = Executors.newFixedThreadPool(10);
​
        ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
        System.out.println(pool.getPoolSize());//0
​
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "在執行了");
        });
​
        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "在執行了");
        });
​
        System.out.println(pool.getPoolSize());//2
//        executorService.shutdown();
    }
}


1.5 執行緒池-ThreadPoolExecutor

建立執行緒池物件 :

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心執行緒數量,最大執行緒數量,空閒執行緒最大存活時間,任務佇列,建立執行緒工廠,任務的拒絕策略);

程式碼實現 :

package com.itheima.mythreadpool;
​
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
​
public class MyThreadPoolDemo3 {
//    引數一:核心執行緒數量
//    引數二:最大執行緒數
//    引數三:空閒執行緒最大存活時間
//    引數四:時間單位
//    引數五:任務佇列
//    引數六:建立執行緒工廠
//    引數七:任務的拒絕策略
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
​
        pool.shutdown();
    }
}

1.6 執行緒池-引數詳解

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
    
corePoolSize:   核心執行緒的最大值,不能小於0
maximumPoolSize:最大執行緒數,不能小於等於0,maximumPoolSize >= corePoolSize
keepAliveTime:  空閒執行緒最大存活時間,不能小於0
unit:           時間單位
workQueue:      任務佇列,不能為null
threadFactory:  建立執行緒工廠,不能為null      
handler:        任務的拒絕策略,不能為null  

1.7 執行緒池-非預設任務拒絕策略

RejectedExecutionHandler是jdk提供的一個任務拒絕策略介面,它下面存在4個子類。

ThreadPoolExecutor.AbortPolicy:           丟棄任務並丟擲RejectedExecutionException異常。是預設的策略。
ThreadPoolExecutor.DiscardPolicy: 丟棄任務,但是不丟擲異常 這是不推薦的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 拋棄佇列中等待最久的任務 然後把當前任務加入佇列中。
ThreadPoolExecutor.CallerRunsPolicy: 呼叫任務的run()方法繞過執行緒池直接執行。

注:明確執行緒池對多可執行的任務數 = 佇列容量 + 最大執行緒數

案例演示1:演示ThreadPoolExecutor.AbortPolicy任務處理策略

public class ThreadPoolExecutorDemo01 {
​
    public static void main(String[] args) {
​
        /**
         * 核心執行緒數量為1 , 最大執行緒池數量為3, 任務容器的容量為1 ,空閒執行緒的最大存在時間為20s
         */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;
​
        // 提交5個任務,而該執行緒池最多可以處理4個任務,當我們使用AbortPolicy這個任務處理策略的時候,就會丟擲異常
        for(int x = 0 ; x < 5 ; x++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "---->> 執行了任務");
            });
        }
    }
}

控制檯輸出結果

pool-1-thread-1---->> 執行了任務
pool-1-thread-3---->> 執行了任務
pool-1-thread-2---->> 執行了任務
pool-1-thread-3---->> 執行了任務

控制檯報錯,僅僅執行了4個任務,有一個任務被丟棄了

案例演示2:演示ThreadPoolExecutor.DiscardPolicy任務處理策略

public class ThreadPoolExecutorDemo02 {
    public static void main(String[] args) {
        /**
         * 核心執行緒數量為1 , 最大執行緒池數量為3, 任務容器的容量為1 ,空閒執行緒的最大存在時間為20s
         */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;
​
        // 提交5個任務,而該執行緒池最多可以處理4個任務,當我們使用DiscardPolicy這個任務處理策略的時候,控制檯不會報錯
        for(int x = 0 ; x < 5 ; x++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "---->> 執行了任務");
            });
        }
    }
}

控制檯輸出結果

pool-1-thread-1---->> 執行了任務
pool-1-thread-1---->> 執行了任務
pool-1-thread-3---->> 執行了任務
pool-1-thread-2---->> 執行了任務

控制檯沒有報錯,僅僅執行了4個任務,有一個任務被丟棄了

案例演示3:演示ThreadPoolExecutor.DiscardOldestPolicy任務處理策略

public class ThreadPoolExecutorDemo02 {
    public static void main(String[] args) {
        /**
         * 核心執行緒數量為1 , 最大執行緒池數量為3, 任務容器的容量為1 ,空閒執行緒的最大存在時間為20s
         */
        ThreadPoolExecutor threadPoolExecutor;
        threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardOldestPolicy());
        // 提交5個任務
        for(int x = 0 ; x < 5 ; x++) {
            // 定義一個變數,來指定指定當前執行的任務;這個變數需要被final修飾
            final int y = x ;
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "---->> 執行了任務" + y);
            });     
        }
    }
}

控制檯輸出結果

pool-1-thread-2---->> 執行了任務2
pool-1-thread-1---->> 執行了任務0
pool-1-thread-3---->> 執行了任務3
pool-1-thread-1---->> 執行了任務4

由於任務1線上程池中等待時間最長,因此任務1被丟棄。

案例演示4:演示ThreadPoolExecutor.CallerRunsPolicy任務處理策略

public class ThreadPoolExecutorDemo04 {
    public static void main(String[] args) {
​
        /**
         * 核心執行緒數量為1 , 最大執行緒池數量為3, 任務容器的容量為1 ,空閒執行緒的最大存在時間為20s
         */
        ThreadPoolExecutor threadPoolExecutor;
        threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.CallerRunsPolicy());
​
        // 提交5個任務
        for(int x = 0 ; x < 5 ; x++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "---->> 執行了任務");
            });
        }
    }
}

控制檯輸出結果

pool-1-thread-1---->> 執行了任務
pool-1-thread-3---->> 執行了任務
pool-1-thread-2---->> 執行了任務
pool-1-thread-1---->> 執行了任務
main---->> 執行了任務

通過控制檯的輸出,我們可以看到次策略沒有通過執行緒池中的執行緒執行任務,而是直接呼叫任務的run()方法繞過執行緒池直接執行。

from:黑馬