【多線程】線程池基本知識

上篇文章講了下線程的建立及一些經常使用的方法,可是在使用的時候,大多數是採用了線程池來管理線程的建立,運行,銷燬等過程。本篇將着重講線程池的基礎內容,包括經過線程池建立線程,線程池的基本信息等。java

建立線程

前期準備

本小節全部代碼都是在 CreateThreadByPool 類上,該類還有一個內部類 MyThread 實現了 Runnable 接口。

首先先把基本的代碼給寫出來ide

public class CreateThreadByPool {
    public static void main(String[] args) {

    }
}

class MyThread implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " processing");
        process();
        System.out.println(Thread.currentThread().getName() + " end");
    }

    private void process() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return String.format("MyThread{%s}", Thread.currentThread().getName());
    }
}

先來大概回顧一下,當咱們想建立10個線程的時候的代碼普通方式是怎樣的函數

private static void createThreadByNormalWay() {
    for (int i = 0; i < 10; i++) {
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread);
        thread.start();
    }
}

能看到的代碼中,是使用了start() 本身直接開啓了線程,可是若是用線程池方式來呢this

經過Executors

第一種建立線程池的方法是經過Executors 類的靜態方法來構建,經過這種方式總共能夠建立4種線程池spa

而且能夠發現返回是ExecutorService ,因此還要接受返回值,最後經過execute 來啓動線程線程

private static void createThreadByPool() {
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 10; i++) {
        MyThread myThread = new MyThread();
        executorService.execute(myThread);
    }
}

先無論底層是如何實現的,至少代碼上是把線程交給了線程池來執行,這樣可以保證線程可以統一管理。code

簡單的比喻就是前者是要你本身去找班長簽到,後者是班長統一管理這整個班的簽到。在main函數中調用看看普通方法和經過線程池建立的線程有什麼區別orm

threadPool1

能夠很明顯的看到有如下幾點區別繼承

  • 線程的名字都不同
  • 而且普通方式是建立了10個線程,然後者只是建立了5個線程(是由咱們本身設定的
  • 前者基本上是10個線程都是同時處理,後者是最多隻能處理5個線程,須要等線程執行完有空閒才能處理其它線程。

經過ThreadPoolExecutor

除了使用Executors.newFixedThreadPool() 建立線程池,還能夠經過new ThreadPoolExecutor() ,這裏可能有的小夥伴會迷糊了,怎麼上面放回的類是ExecutorService ,如今返回的又是ThreadPoolExecutor ,其實二者是同一個東西。接口

能夠看到ThreadExecutorPool 是繼承了 AbstractExecutorService ,然後者是實現了ExecutorService 。經過該方法建立的線程池的代碼以下

能夠先這樣運行體驗下,至於說構造函數裏面不一樣參數的含義,在後面的篇幅中會說到,到時候再返回來看便可。
private static void createThreadByThreadPoolExecutor() {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
    for (int i = 0; i < 10; i++) {
        MyThread myThread = new MyThread();
        executor.execute(myThread);
    }
}

看下運行結果

輸出結果沒啥好講的,可是若是細心的小夥伴在上一個gif就會發現,經過線程池來啓動線程的方式,程序並無退出,會一直運行。這是由於咱們沒有shutdown 線程池。

二者區別

回過頭來看看Executors.靜態方法 這種方法來建立線程池的源碼

能夠看到其實更深一層仍是使用了new ThreadPoolExecutor() ,只不過咱們本身能定製的構造函數的參數變得極其少,這時候確定有小夥伴疑問了,那爲何不直接都用new ThreadPoolExecutor() 呢?

《阿里java開發手冊》 嵩山版明確規定了兩點,一是線程資源必須經過線程池提供,不容許自行顯式建立線程;二是線程池不容許使用Executors去建立,而是經過ThreadPoolExecutor的方式去建立。

着重看第二點強制經過ThreadPoolExecutor的方式來建立線程,緣由在下面也有,來看看FixedThreadPool和SingleThreadPool的源碼

image-20210629161632466

其它的無論,能夠看到二者調用構造函數中的隊列都是LinkedBlockingQueue ,這個隊列是無邊界的,因此有了容許請求長度爲Integer.MAX_VALUE ,會堆積大量的請求 ,從而致使OOM。

再來看看CachedThreadPool的源碼

注意這裏構造函數的第二個參數是線程池最大線程數,它設置成了Integer.MAX_VALUE ,這就可能會建立大量的線程,從而致使OOM。

線程池信息

ThreadPoolExecutor

上面也能夠看到,建立線程池最重要也是最應該使用的方法的是new ThreadPoolExecutor() ,接下來把重點放在ThreadPoolExecutor這個類上面

這個是類中的全部的屬性,接下來再看看構造函數

有4種,可是歸根結底只有如下這一種構造函數,講下這些參數的意義,而後你們就能夠回頭看下上一小節的例子。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    //省略實現
}
  • corePoolSize :核心線程數,大白話就是可以工做的線程數量
  • maximumPoolSize :最大線程數,就是這個線程池能容納線程的數量
  • keepAliveTime :存活時間,當線程池中的線程數量大於核心線程數的時候,若是時候沒有任務提交,核心線程池外的線程不會當即被銷燬,而是會等待,直到等待的時間超過了這個字段纔會被回收銷燬
  • unit :存活時間的單位
  • workQueue :工做隊列,就是在線程開始被調用前,就是存在這個隊列中
  • threadFactory :線程工廠,執行程序建立新線程時使用的工廠
  • handler :拒絕策略,當達到線程邊界和隊列容量而採起的拒絕策略

對於這個拒絕策略,簡單說下,有四種實現。

實現 RejectedExecutionHandler 接口就能實現本身的拒絕策略

監控線程

下面就來簡單實現一個本身的拒絕策略,而且來看下上述類中屬性的信息

首先須要一個監控線程類

class MonitorThread implements Runnable {
    
    //注入一個線程池
    private ThreadPoolExecutor executor;

    public MonitorThread(ThreadPoolExecutor executor) {
        this.executor = executor;
    }

    private boolean monitor = true;

    public void stopMonitor() {
        monitor = false;
    }

    @Override
    public void run() {
        //監控一直運行,每3s輸出一次狀態
        while (monitor) {
            //主要邏輯是監控線程池的狀態
            System.out.println(
                    String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s, rejectedExecutionHandler: %s",
                            this.executor.getPoolSize(),
                            this.executor.getCorePoolSize(),
                            this.executor.getActiveCount(),
                            this.executor.getCompletedTaskCount(),
                            this.executor.getTaskCount(),
                            this.executor.isShutdown(),
                            this.executor.isTerminated(),
                            this.executor.getRejectedExecutionHandler()));

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

同時實現自定義的拒絕策略

其實這仍是沒有對r處理,拒絕了就拒絕了,只是打印出來,可是並無實質性地處理
class MyRejectedExecutionHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("task is rejected");
    }
}

接下來就是public類TheradPoolInfo注意工做線程採用的是上一小節的MyThread

public class ThreadPoolInfo {
    public static void main(String[] args) throws InterruptedException {
        //新建了一個線程池,核心線程數是3,最大線程數是5,30s
        //隊列是ArrayBlockingQueue,而且大小邊界是3,拒絕策略自定義輸出一句話
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3), new MyRejectedExecutionHandler());
        
        //開啓監控線程
        MonitorThread monitorThread = new MonitorThread(executor);
        new Thread(monitorThread).start();
        
        //開啓工做線程
        for (int i = 0; i < 10; i++) {
            executor.execute(new MyThread());
        }
        
        //關閉線程池和監控線程
        Thread.sleep(12000);
        executor.shutdown();
        Thread.sleep(3000);
        monitorThread.stopMonitor();
    }
}

預期結果: 經過構造函數能夠知道,預期是有3個核心線程執行任務,會拒絕2個線程,完成8個任務(最大線程數是5,隊列長度是3,具體會在下一篇文章中講)。

能夠看到結果和預期的同樣

創做不易,若是對你有幫助,歡迎點贊,收藏和分享啦!

下面是我的公衆號,有興趣的能夠關注一下,說不定就是你的寶藏公衆號哦,基本2,3天1更技術文章!!!

相關文章
相關標籤/搜索