【詳解】Executors框架之ThreadPoolExecutor

引出

摘自阿里巴巴開發手冊

【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣 的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。 說明:Executors 返回的線程池對象的弊端如下:

  • 1)FixedThreadPool 和 SingleThreadPool: 允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
  • 2)CachedThreadPool 和 ScheduledThreadPool: 允許的創建線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。

線程池創建的參數

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize:線程池始終線程數,即使有些是空閒的。設置allowCoreThreadTimeOut參數爲true,纔會進行回收。
  • maximumPoolSize:線程池最大線程數,表示在線程池中最多能創建多少個線程。如果當線程池中的數量到達這個數字時,新來的任務會拋出異常。
  • keepAliveTime:表示線程沒有任務執行時最多能保持多少時間會回收,然後線程池的數目維持在corePoolSize。
  • unit:參數keepAliveTime的時間單位
  • workQueue:一個阻塞隊列,所有的任務都會先放在這裏,務;如果對當前對線程的需求超過了corePoolSize大小,會用來存儲等待執行的任。
  • threadFactory:線程工廠,主要用來創建線程,比如指定線程的名字。
  • handler:如果線程池已滿,新的任務處理方式。

注意一點初始化線程池時,線程數爲0

測試

測試點

  • coreSize = 1,MaxSize=2,阻塞隊列爲1,如果提交三個任務會怎麼樣?
  • coreSize = 1,MaxSize=2,阻塞隊列大小爲5,如果提交七個任務會怎麼樣?
  • coreSize = 1,MaxSize=2,阻塞隊列大小爲5,如果提交八個任務會怎麼樣?

測試代碼

public class ThreadPoolExecutorBuild {


    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = bulidThreadPoolExecutor();

        int activeCount = -1;
        int queueSize = -1;

        while (true){
            if (activeCount!=threadPoolExecutor.getActiveCount() || queueSize !=threadPoolExecutor.getQueue().size()){
                activeCount = threadPoolExecutor.getActiveCount();
                queueSize = threadPoolExecutor.getQueue().size();
                System.out.println("活躍的線程數:"+threadPoolExecutor.getActiveCount());
                System.out.println("getCorePoolSize: "+threadPoolExecutor.getCorePoolSize());
                System.out.println("阻塞隊列的任務數:"+threadPoolExecutor.getQueue().size());
                System.out.println("最大的線程數:"+threadPoolExecutor.getMaximumPoolSize());
                System.out.println("======================");
            }

        }
    }



    private static ThreadPoolExecutor bulidThreadPoolExecutor() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,
                30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(1), Thread::new,new ThreadPoolExecutor.AbortPolicy());
        System.out.println("The thread pool creat done.");

        threadPoolExecutor.execute(()->sleepSeconds(100));
        threadPoolExecutor.execute(()->sleepSeconds(100));
        threadPoolExecutor.execute(()->sleepSeconds(100));

        return threadPoolExecutor;
    }


    private static void sleepSeconds(long sec){
        try {
            System.out.println(" >>> " + Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(sec);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

結果

測試一結果

The thread pool creat done.
 >>> Thread-0
 >>> Thread-1
活躍的線程數:2
getCorePoolSize: 1
阻塞隊列的任務數:1
最大的線程數:2
======================

測試二結果

The thread pool creat done.
 >>> Thread-0
 >>> Thread-1
活躍的線程數:2
getCorePoolSize: 1
阻塞隊列的任務數:5
最大的線程數:2
======================

測試三結果

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task ThreadPool.ThreadPoolExecutorBuild$$Lambda$9/2065951873@6acbcfc0 rejected from java.util.concurrent.ThreadPoolExecutor@5f184fc6[Running, pool size = 2, active threads = 2, queued tasks = 5, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at ThreadPool.ThreadPoolExecutorBuild.bulidThreadPoolExecutor(ThreadPoolExecutorBuild.java:44)
	at ThreadPool.ThreadPoolExecutorBuild.main(ThreadPoolExecutorBuild.java:11)

結論:

  • 當阻塞隊列滿時,纔會創建新的線程
  • 當線程有空閒時,超過一定時間會被回收
  • 當線程數達到最大值,且阻塞隊列滿時,會執行拒絕策略
  • 核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。

線程池的阻塞隊列的選擇?

1. ArrayBlockingQueue
2. LinkedBlockingQueue
3. PriorityBlockingQueue
4. SynchronousQueue
  • ArrayBlockingQueue:是一個有邊界的阻塞隊列,它的內部實現是一個數組。它的容量在初始化時就確定不變。
  • LinkedBlockingQueue:阻塞隊列大小的配置是可選的,其內部實現是一個鏈表。
  • PriorityBlockingQueue:是一個沒有邊界的隊列,所有插入到PriorityBlockingQueue的對象必須實現java.lang.Comparable接口,隊列優先級的排序就是按照我們對這個接口的實現來定義的。
  • SynchronousQueue:隊列內部僅允許容納一個元素。當一個線程插入一個元素後會被阻塞,除非這個元素被另一個線程消費。

workQueue常用的是java.util.concurrent.ArrayBlockingQueue

拒絕策略

handler有四個選擇

  • ThreadPoolExecutor.AbortPolicy(): 拋出java.util.concurrent.RejectedExecutionException異常
  • ThreadPoolExecutor.CallerRunsPolicy():重試添加當前的任務,他會自動重複調用execute()方法
  • ThreadPoolExecutor.DiscardOldestPolicy():拋棄舊的任務
  • ThreadPoolExecutor.DiscardPolicy(): 拋棄當前的任務

關閉

關閉有兩個方法:shutdownshutdownNow

shutdown

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}


private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}  


private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

  • 從源碼可以看出,本質上執行的是interrupt方法
  • 如果線程是空閒的,執行的是Condition的await的方法,會被直接打斷,被回收
  • 如果正在工作,該線程會被打上一個標記,等任務執行後被回收

shutdownNow

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();//先打斷
        tasks = drainQueue();//再把任務隊列沒有執行的任務取出
    } finally {
        mainLock.unlock();
    }
    tryTerminate();//不斷的打斷
    return tasks;
}
  • 先打斷空閒的打斷
  • 然後清空任務隊列
  • 然後不斷的嘗試打斷正在執行的線程
  • 最後會返回一個List集合,包含還沒有執行的任務

守護線程

如果有些任務執行時間很長,想要關閉,可以利用守護線程的方式強制關閉。

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,
        30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5), r -> {
    Thread t = new Thread(r);
    t.setDaemon(true);
    return t;
},new ThreadPoolExecutor.AbortPolicy());

被守護的線程是主線程,只要主線程執行完成,線程池就會強制關閉,可以配合awaitTermination方法使用

public static void main(String[] args) throws InterruptedException {
    ThreadPoolExecutor threadPoolExecutor = bulidThreadPoolExecutor();
    threadPoolExecutor.shutdown();
    threadPoolExecutor.awaitTermination(5,TimeUnit.SECONDS);
    System.out.println("強制關閉");
}

一個關閉的陷阱

  • 當我們關閉線程時,如果一個線程正在執行,被打斷後,也會被系統認爲完成了任務
  • 當使用shutdownNow時,不會返回到集合中,此時該任務就莫名的消失了

API

關閉核心線程

public void allowCoreThreadTimeOut(boolean value)
  • 當賦值爲true時,核心線程在空閒的時候也會被銷燬

更改線程池核心參數

public void setCorePoolSize(int corePoolSize)

public void setMaximumPoolSize(int maximumPoolSize)

public void setKeepAliveTime(long time,
                             TimeUnit unit)
public void setThreadFactory(ThreadFactory threadFactory)

public void setRejectedExecutionHandler(RejectedExecutionHandler handler)

除了阻塞隊列,可以看出線程池參數都是可以更改的

創建初始線程

public int prestartAllCoreThreads()

默認線程數是0,使用後,會初始化到核心線程數

測試

public class ExecutorServiceTest {
    public static void main(String[] args) {
        ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
        executorService.prestartAllCoreThreads();
        int poolSize = executorService.getPoolSize();
        System.out.println(poolSize);

    }
}

結果

5

清空阻塞隊列

public void purge()
  • 所有沒有執行的任務會被清空
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章