近萬字,就是爲了和你聊聊線程池

更多精彩請關注公衆號xhJaver,京東java工程師和你一塊兒成長

咱們知道,在計算機中建立一個線程和銷燬一個線程都是十分耗費資源的操做,有一種思想叫作,池化思想,就是說咱們建立個池子,把耗費資源的操做都提早作好,後面你們一塊兒用建立好的東西,最後統一銷燬。省去了用一次建立一次,銷燬一次,這種耗費資源的操做。java

1、線程池工做原理

線程池就是這種思想,他的基本工做流程以下圖所示
image數組

那麼他的核心線程,任務隊列,這些又是什麼呢?怎麼設置呢?安全

這些就要從代碼入手了,咱們先來看下線程池構造方法的代碼ide

2、線程池構造方法

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.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

其實ThreadPoolExecutor有四種構造方法,不過底層都是用這個7個參數的構造方法,因此咱們弄懂這一個就行了,如下是其餘構造方法的底層實現測試

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

其中默認的拒絕策略是this

private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

這些構造方法中的atom

this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);

就是那七個參數的構造方法spa

有點懵?不要緊,接下來咱們一個個的解析這七個參數的意思線程

3、線程池參數介紹

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.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

1. 第一個參數 corePoolSize 表明這個線程池的核心線程數日誌

2. 第二個參數 maximumPoolSize 表明這個線程池的最大線程數 (核心線程數 +非核心線程數)

3. 第三個參數 keepAliveTime 表明這個線程池的非核心線程的空閒時的存活時間

4. 第四個參數 unit 表明這個線程池的非核心線程的空閒存活時間的單位

5. 第五個參數 workQueue 表明這個線程池的任務阻塞隊列,jdk中有幾種常見的阻塞隊列

  • ArrayBlockingQueue:基於數組結構的有界阻塞隊列
  • LinkedBlockingQueue:是一個基於鏈表結構的阻塞隊列
  • SynchronousQueue :同步隊列,只存儲一個任務,插入任務時要等待(若是隊列裏有元素的話)取出任務時要等待(若是隊列裏沒有元素的話)
  • PriorityBlockingQueue:優先級隊列,進入隊列的元素按照優先級會進行排序
建議:建議使用有界隊列,要是無界隊列的話,任務太多的話可能會致使OOM

6. 第六個參數 threadFactory(能夠自定義) 表明這個線程池的建立線程的工廠,有兩種

  • Executors.privilegedThreadFactory() 使用訪問權限建立一個權限控制的線程。
  • Executors.defaultThreadFactory() 將建立一個同線程組且默認優先級的線程

7. 第七個參數 handler(能夠自定義) 表明這個線程池的拒絕處理任務的飽和策略,jdk默認提供了四種

  • new ThreadPoolExecutor.AbortPolicy(); 直接拋出異常
  • new ThreadPoolExecutor.CallerRunsPolicy(); 用當前調用者的線程中處理傳過來的任務
  • new ThreadPoolExecutor.DiscardOldestPolicy(); 丟棄最老的一個任務,而後把傳過來的任務加入到阻塞隊列中
  • new ThreadPoolExecutor.DiscardPolicy(); 什麼都不作,直接丟掉傳過來的任務

4、使用線程池例子

基礎概念也都看了,下面來看個使用線程池處理任務的小例子

首先,咱們先建立個任務類

public class Task implements Runnable {
    private String taskName;
    
    public Task(String taskName) {
        this.taskName = taskName;
    }
    @Override
    public void run() {
        try {
            //模擬每一個任務的耗時
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String name = Thread.currentThread().getName();
        System.out.println("這裏是xhJaver,線程池系列 當前線程名字是 " + name+"  處理了  "+ taskName+"  任務");
    }
}

咱們再來看測試類

public class Demo1 {
    public static void main(String[] args) {
        //阻塞隊列,設置阻塞任務最多爲10個
        BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable> (10);
        //線程工廠
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        //拒絕策略 當線程池的最大工做線程跑滿以及阻塞隊列滿了的話,會由拒絕策略處理剩下的任務
        ThreadPoolExecutor.AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy();
        //建立線程池  核心線程數爲5  最大線程數爲10 非核心線程空閒存活時間爲60s
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 60L,
                        TimeUnit.SECONDS, blockingQueue, threadFactory, abortPolicy
        );
        for (int i=0;i<10;i++){
            //建立10個任務,若是要是建立>20個任務,則20之外的任務會交由拒絕策略處理
            Task task = new Task("task" + i);
            //讓咱們自定義的線程池去跑這些任務
            threadPoolExecutor.execute(task);
        }
         //記得要關閉線程池
        threadPoolExecutor.shutdown();
    }
}

輸出結果是

這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task0  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-2  處理了  task1  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-3  處理了  task2  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-4  處理了  task3  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-5  處理了  task4  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task5  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-2  處理了  task6  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-5  處理了  task9  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-4  處理了  task8  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-3  處理了  task7  任務

5、線程工廠是什麼 ?

文中一直說線程工廠線程工廠,這線程工場究竟是幹嗎的呢? 固然是建立線程的工廠啦,建立線程,線程固然得有個名字咯,就像剛纔的小例子輸出的同樣,線程的名字是pool-1-thread-3等等,我如今不想叫這個名字了,那就叫thread-xhJaver吧,這是自定義的名字,那怎麼自定義呢?

首先,要實現ThreadFactory接口中的Thread newThread(Runnable r)方法, 傳入一個任務,返回一個自定義線程,以下面的代碼同樣

public class DIYThreadFactory implements ThreadFactory {
    private AtomicInteger atomicInteger;
    public  DIYThreadFactory( AtomicInteger atomicInteger){
         this.atomicInteger =  atomicInteger;
    }
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setName("xhJaver-thread-"+atomicInteger.getAndIncrement());
        return thread;
    }
}

而後在使用時傳入這個自定義的線程工廠

public static void main(String[] args) {
 //阻塞隊列,設置阻塞任務最多爲10個
BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable> (10);
 //建立線程安全的計數器
 AtomicInteger atomicInteger = new AtomicInteger();
 //自定義線程工廠
 ThreadFactory threadFactory = new DIYThreadFactory(atomicInteger);
 //拒絕策略 當線程池的最大工做線程跑滿以及阻塞隊列滿了的話,會由拒絕策略處理剩下的任務
ThreadPoolExecutor.AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy();
 //建立線程池  核心線程數爲5  最大線程數爲10 非核心線程空閒存活時間爲60s
 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 60L,
   TimeUnit.SECONDS, blockingQueue, threadFactory, abortPolicy
 );
 for (int i=0;i<10;i++){
  //建立10個任務,若是要是建立>20個任務,則20之外的任務會交由拒絕策略處理
  Task task = new Task("task" + i);
  //讓咱們自定義的線程池去跑這些任務
  threadPoolExecutor.execute(task);
 }
 //記得要關閉線程池
 threadPoolExecutor.shutdown();
}

輸出結果是

這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-0  處理了  task0  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-1  處理了  task1  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-4  處理了  task4  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-3  處理了  task3  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-2  處理了  task2  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-0  處理了  task5  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-1  處理了  task6  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-2  處理了  task9  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-3  處理了  task8  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-4  處理了  task7  任務

我也學會了自定義線程工廠了,可自定義名字到底有用呢,固然是排查問題啊!把線程名字定義爲和本身業務有關的名字,到時候報錯的時候就方便排查了。

6、 拒絕策略是什麼

線程工廠能夠自定義,那拒絕策略能夠自定義嗎?固然能夠啦 方法以下,首先也要實現一個RejectedExecutionHandler接口,重寫rejectedExecution 這個方法

public class DIYRejectedHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    //記錄日誌等操做
        System.out.println("這是xhJaver沒法處理的任務  "+r.toString()+"  當前線程名字是 "+Thread.currentThread().getName());
    }
}

而後在使用時傳入這個自定義的拒絕策略

public static void main(String[] args) {
 //阻塞隊列,設置阻塞任務最多爲10個
 BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable> (10);
 //建立線程安全的計數器
 AtomicInteger atomicInteger = new AtomicInteger();
 //自定義線程工廠
 ThreadFactory threadFactory = new DIYThreadFactory(atomicInteger);
 //自定義拒絕策略 當線程池的最大工做線程跑滿以及阻塞隊列滿了的話,會由拒絕策略處理剩下的任務
 DIYRejectedHandler diyRejectedHandler = new DIYRejectedHandler();
 //建立線程池  核心線程數爲5  最大線程數爲10 非核心線程空閒存活時間爲60s
 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 60L,
   TimeUnit.SECONDS, blockingQueue, threadFactory, diyRejectedHandler
 );
 for (int i=0;i<30;i++){
  //建立10個任務,若是要是建立>20個任務,則20之外的任務會交由拒絕策略處理
  Task task = new Task("task" + i);
  //讓咱們自定義的線程池去跑這些任務
  threadPoolExecutor.execute(task);
 }
 //記得要關閉線程池
 threadPoolExecutor.shutdown();
}

輸出結果是

這是xhJaver沒法處理的任務  Task{taskName='task20'}  當前線程名字是 main
這是xhJaver沒法處理的任務  Task{taskName='task21'}  當前線程名字是 main
這是xhJaver沒法處理的任務  Task{taskName='task22'}  當前線程名字是 main
這是xhJaver沒法處理的任務  Task{taskName='task23'}  當前線程名字是 main
這是xhJaver沒法處理的任務  Task{taskName='task24'}  當前線程名字是 main
這是xhJaver沒法處理的任務  Task{taskName='task25'}  當前線程名字是 main
這是xhJaver沒法處理的任務  Task{taskName='task26'}  當前線程名字是 main
這是xhJaver沒法處理的任務  Task{taskName='task27'}  當前線程名字是 main
這是xhJaver沒法處理的任務  Task{taskName='task28'}  當前線程名字是 main
這是xhJaver沒法處理的任務  Task{taskName='task29'}  當前線程名字是 main
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-5  處理了  task15  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-4  處理了  task4  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-3  處理了  task3  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-2  處理了  task2  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-1  處理了  task1  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-0  處理了  task0  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-9  處理了  task19  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-8  處理了  task18  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-7  處理了  task17  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-6  處理了  task16  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-4  處理了  task6  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-5  處理了  task5  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-3  處理了  task7  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-2  處理了  task8  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-1  處理了  task9  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-0  處理了  task10  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-9  處理了  task11  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-8  處理了  task12  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-7  處理了  task13  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-6  處理了  task14  任務

7、常見的阻塞隊列及注意點

由於阻塞隊列的知識太多了,後續咱們會單獨開篇來說這個阻塞隊列,先介紹幾個經常使用的

1.ArrayBlockingQueue 基於數組的有界隊列

2.LinkedBlockingQueue 基於鏈表的無界隊列

3.SynchronousQueue

它內部只有一個元素,插入時若是發現內部有元素未被取走則阻塞,取元素時若隊列沒有元素則被阻 塞,直到有元素插入進來。

搭配線程池使用以下 ,先建立任務類

public class Task implements Runnable {
    private String taskName;
    public Task(String taskName) {
        this.taskName = taskName;
    }
    @Override
    public String toString() {
        return "Task{" +
                "taskName='" + taskName + ''' +
                '}';
    }
    @Override
    public void run() {
        try {
            //模擬每一個任務的耗時
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String name = Thread.currentThread().getName();
        System.out.println("這裏是xhJaver,線程池系列 當前線程名字是 " + name+"  處理了  "+ taskName+"  任務");
    }
}

再使用阻塞隊列

public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i=0;i<10;i++){
            //建立十個任務
            Task task = new Task("task" + i);
            //去跑任務
            executorService.execute(task);
        }
         //記得要關閉線程池
        executorService.shutdown();
    }

其中newCachedThreadPool底層就使用的是SynchronousQueue

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

輸出結果是

這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task0  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-2  處理了  task1  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-5  處理了  task4  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-4  處理了  task3  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-3  處理了  task2  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-6  處理了  task5  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-7  處理了  task6  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-10  處理了  task9  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-9  處理了  task8  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-8  處理了  task7  任務

因而可知,線程池分別建立了十個線程來處理這十個任務,爲何呢? 這是由於,我每一個任務的模擬處理時間是1s,當再來的任務發現阻塞隊列中有任務還沒被取走,就建立非核心線程處理剛來的這個任務,不斷的來任務,不斷的建立線程,因此用這個阻塞隊列再搭配線程池的總線程數等參數設置可能會由於不斷的建立線程而致使OOM。

4.PriorityBlockingQueue 優先級隊列 進入隊列的元素會按照任務的優先級排序。而且必須實現Comparable接口。

參數:priorityTask - 要比較的對象。 返回:負整數、零或正整數, 根據此對象是小於、等於仍是大於指定對象(要比較的對象)。

先建立一個帶有優先級的任務

public class PriorityTask implements Runnable , Comparable<PriorityTask>{
    private String taskName;
    // 優先級,根據這個數進行排序
    private Integer priority;
    public PriorityTask(Integer priority,String taskName) {
        this.priority = priority;
        this.taskName = taskName;
    }
    
    //這個compareTo方法的返回值若是是-1的話,則排序會認爲傳過來的任務比此任務的大,降序排列
    //這個compareTo方法的返回值若是是1的話,則排序會認爲傳過來的任務比此任務的小,升序排列
    @Override
    public int compareTo(PriorityTask priorityTask) {
        //Integer.compare返回 -1表明 傳過來的任務的priority 比次任務的priority要小
        // Integer.compare 0   傳過來的任務的priority 比次任務的priority同樣大
        //Integer.compare  1   傳過來的任務的priority 比次任務的priority要大
        return Integer.compare(priorityTask.priority,this.priority);
    }
    @Override
    public void run() {
        try {
            //模擬每一個任務的耗時
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String name = Thread.currentThread().getName();
        System.out.println("這裏是xhJaver,線程池系列 當前線程名字是 " + name+"  處理了  "+ taskName+"  任務");
    }
    @Override
    public String toString() {
        return "Task{" +
                "taskName='" + taskName + ''' +
                '}';
    }
}

Integer.compare 的 比較大小代碼

java
   public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }

測試代碼

public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
                60L, TimeUnit.SECONDS,
                new PriorityBlockingQueue());
        for (int i=0;i<5;i++){
            //建立十個任務
            PriorityTask priorityTask = new PriorityTask(i,"task" + i);
            //去跑任務
            threadPoolExecutor.execute(priorityTask);
        }
        for (int i=100;i>=95;i--){
            //建立十個任務
            PriorityTask priorityTask = new PriorityTask(i,"task" + i);
            //去跑任務
            threadPoolExecutor.execute(priorityTask);
        }
         //記得要關閉線程池
        threadPoolExecutor.shutdown();
    }

輸出結果是

這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task0  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task100  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task99  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task98  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task97  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task96  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task95  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task4  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task3  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task2  任務
這裏是xhJaver,線程池系列 當前線程名字是 pool-1-thread-1  處理了  task1  任務

由輸出結果可見,除了第一個之外,處理任務的順序會按照優先級大小先處理

8、幾種常見的線程池及注意點

他們分別是如下幾種

1.newFixedThreadPool

  • Executors.newFixedThreadPool(10) 它的構造方法是
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

有此可見,這個FixedThreadPool線程池的核心線程數和最大線程數同樣,因此就沒有非核心線程數,存活時間這個參數也就是無效的了,它底層用的是LinkedBlockingQueue這個阻塞隊列,這個隊列是個無界隊列,能夠點進去源碼看它默認的容量是Integer.MAX_VALUE

public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

因此這會致使一個什麼問題呢?就會致使,當核心線程都跑滿的時候,再來新任務的話就會不斷的添加至這個阻塞隊列裏面,一直加一直加,可是內存是有限的,因此有可能會出現 OOM(OutOfMemory) 的問題

  • Executors.newFixedThreadPool(10,Executors.defaultThreadFactory()); 這個構造方法能夠傳過來指定的建立線程的工廠
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

2.newCachedThreadPool

  • Executors.newCachedThreadPool() 它的構造方法是
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

因而可知,它的核心線程數默認是0,線程池總線程容量是Integer.MAX_VALUE,阻塞隊列用的是SynchronousQueue同步隊列,非核心線程數的空閒存活時間爲60s,這會致使一個什麼問題呢?只要來了一個任務,若是沒有線程的話就建立一個非核心線程去跑這個任務,若是跑着的過程當中又來了一個任務,就會繼續建立線程去跑,以此類推,內存是有限的,不斷的建立線程的話也會觸發OOM問題

  • Executors.newCachedThreadPool(Executors.defaultThreadFactory()) 這個構造方法能夠傳過來指定的建立線程的工廠
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

3.newSingleThreadExecutor

  • Executors.newSingleThreadExecutor() 它的構造方法是
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

咱們能夠看出,它的核心線程數是一個,總線程數也是一個。底層用的是LinkedBlockingQueue阻塞隊列 當來任務的時候線程池若是沒有線程的話,則建立一個也是惟一一個線程來執行任務,剩下的任務都會被塞進無界阻塞隊列裏面,也是會有可能產生OOM問題。

  • Executors.newSingleThreadExecutor(Executors.defaultThreadFactory()) 這個構造方法能夠傳過來指定的建立線程的工廠
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

9、拓展線程池

什麼?線程池還能夠拓展?!是的,若是我想記錄下每一個任務的執行開始狀況,結束狀況,線程池關閉狀況就要拓展啦,ThreadPoolExecutor它內部是提供了幾個方法給咱們拓展,其中beforeExecute、afterExecute、terminated,這三個分別對應任務開始,任務結束,線程池關閉的三種狀況,因此咱們就要重寫他們啦,話很少說,看下代碼

public static void main(String[] args) {
 //阻塞隊列,設置阻塞任務最多爲10個
 BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable> (10);
 //建立線程安全的計數器
 AtomicInteger atomicInteger = new AtomicInteger();
 //自定義線程工廠
 ThreadFactory threadFactory = new DIYThreadFactory(atomicInteger);
 //自定義拒絕策略 當線程池的最大工做線程跑滿以及阻塞隊列滿了的話,會由拒絕策略處理剩下的任務
 DIYRejectedHandler diyRejectedHandler = new DIYRejectedHandler();
 //建立線程池  核心線程數爲5  最大線程數爲10 非核心線程空閒存活時間爲60s
 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 60L,
   TimeUnit.SECONDS, blockingQueue, threadFactory, diyRejectedHandler
 ){
  @Override
  protected void beforeExecute(Thread t, Runnable r) {
   System.out.println("xhJaver 當前線程是"+t.getName()+"開始處理任務:"+r.toString());
  }
  @Override
  protected void afterExecute(Runnable r, Throwable t) {
   if(t!=null){
    System.out.println("xhJaver 當前線程是"+Thread.currentThread().getName() +"處理任務結束:"+r.toString()+" 錯誤是 "+ t);
   }
   System.out.println("xhJaver 當前線程是"+Thread.currentThread().getName() +"處理任務結束:"+r.toString()+" 沒有錯誤 ");
  }
  @Override
  protected void terminated() {
   System.out.println("xhJaver 當前線程是"+Thread.currentThread().getName() +"關閉線程池");
  }
 };
 for (int i=0;i<21;i++){
  //建立10個任務,若是要是建立>20個任務,則20之外的任務會交由拒絕策略處理
  Task task = new Task("task" + i);
  //讓咱們自定義的線程池去跑這些任務
  threadPoolExecutor.execute(task);
 }
 //記得要關閉線程池
 threadPoolExecutor.shutdown();
}

輸出結果是

這是xhJaver沒法處理的任務  Task{taskName='task20'}  當前線程名字是 main
xhJaver 當前線程是xhJaver-thread-7開始處理任務:Task{taskName='task17'}
xhJaver 當前線程是xhJaver-thread-6開始處理任務:Task{taskName='task16'}
xhJaver 當前線程是xhJaver-thread-9開始處理任務:Task{taskName='task19'}
xhJaver 當前線程是xhJaver-thread-4開始處理任務:Task{taskName='task4'}
xhJaver 當前線程是xhJaver-thread-8開始處理任務:Task{taskName='task18'}
xhJaver 當前線程是xhJaver-thread-2開始處理任務:Task{taskName='task2'}
xhJaver 當前線程是xhJaver-thread-3開始處理任務:Task{taskName='task3'}
xhJaver 當前線程是xhJaver-thread-5開始處理任務:Task{taskName='task15'}
xhJaver 當前線程是xhJaver-thread-0開始處理任務:Task{taskName='task0'}
xhJaver 當前線程是xhJaver-thread-1開始處理任務:Task{taskName='task1'}
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-4  處理了  task4  任務
xhJaver 當前線程是xhJaver-thread-4處理任務結束:Task{taskName='task4'} 沒有錯誤 
xhJaver 當前線程是xhJaver-thread-4開始處理任務:Task{taskName='task5'}
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-9  處理了  task19  任務
xhJaver 當前線程是xhJaver-thread-9處理任務結束:Task{taskName='task19'} 沒有錯誤 
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-6  處理了  task16  任務
xhJaver 當前線程是xhJaver-thread-6處理任務結束:Task{taskName='task16'} 沒有錯誤 
xhJaver 當前線程是xhJaver-thread-9開始處理任務:Task{taskName='task6'}
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-7  處理了  task17  任務
xhJaver 當前線程是xhJaver-thread-7處理任務結束:Task{taskName='task17'} 沒有錯誤 
xhJaver 當前線程是xhJaver-thread-7開始處理任務:Task{taskName='task8'}
xhJaver 當前線程是xhJaver-thread-6開始處理任務:Task{taskName='task7'}
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-1  處理了  task1  任務
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-8  處理了  task18  任務
xhJaver 當前線程是xhJaver-thread-8處理任務結束:Task{taskName='task18'} 沒有錯誤 
xhJaver 當前線程是xhJaver-thread-8開始處理任務:Task{taskName='task9'}
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-2  處理了  task2  任務
xhJaver 當前線程是xhJaver-thread-2處理任務結束:Task{taskName='task2'} 沒有錯誤 
xhJaver 當前線程是xhJaver-thread-2開始處理任務:Task{taskName='task10'}
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-3  處理了  task3  任務
xhJaver 當前線程是xhJaver-thread-3處理任務結束:Task{taskName='task3'} 沒有錯誤 
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-5  處理了  task15  任務
xhJaver 當前線程是xhJaver-thread-5處理任務結束:Task{taskName='task15'} 沒有錯誤 
xhJaver 當前線程是xhJaver-thread-5開始處理任務:Task{taskName='task12'}
xhJaver 當前線程是xhJaver-thread-1處理任務結束:Task{taskName='task1'} 沒有錯誤 
xhJaver 當前線程是xhJaver-thread-1開始處理任務:Task{taskName='task13'}
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-0  處理了  task0  任務
xhJaver 當前線程是xhJaver-thread-3開始處理任務:Task{taskName='task11'}
xhJaver 當前線程是xhJaver-thread-0處理任務結束:Task{taskName='task0'} 沒有錯誤 
xhJaver 當前線程是xhJaver-thread-0開始處理任務:Task{taskName='task14'}
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-4  處理了  task5  任務
xhJaver 當前線程是xhJaver-thread-4處理任務結束:Task{taskName='task5'} 沒有錯誤 
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-6  處理了  task7  任務
xhJaver 當前線程是xhJaver-thread-6處理任務結束:Task{taskName='task7'} 沒有錯誤 
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-9  處理了  task6  任務
xhJaver 當前線程是xhJaver-thread-9處理任務結束:Task{taskName='task6'} 沒有錯誤 
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-7  處理了  task8  任務
xhJaver 當前線程是xhJaver-thread-7處理任務結束:Task{taskName='task8'} 沒有錯誤 
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-2  處理了  task10  任務
xhJaver 當前線程是xhJaver-thread-2處理任務結束:Task{taskName='task10'} 沒有錯誤 
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-8  處理了  task9  任務
xhJaver 當前線程是xhJaver-thread-8處理任務結束:Task{taskName='task9'} 沒有錯誤 
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-5  處理了  task12  任務
xhJaver 當前線程是xhJaver-thread-5處理任務結束:Task{taskName='task12'} 沒有錯誤 
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-0  處理了  task14  任務
xhJaver 當前線程是xhJaver-thread-0處理任務結束:Task{taskName='task14'} 沒有錯誤 
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-3  處理了  task11  任務
xhJaver 當前線程是xhJaver-thread-3處理任務結束:Task{taskName='task11'} 沒有錯誤 
這裏是xhJaver,線程池系列 當前線程名字是 xhJaver-thread-1  處理了  task13  任務
xhJaver 當前線程是xhJaver-thread-1處理任務結束:Task{taskName='task13'} 沒有錯誤 
xhJaver 當前線程是xhJaver-thread-1關閉線程池
更多精彩請關注公衆號xhJaver,京東java工程師和你一塊兒成長
相關文章
相關標籤/搜索