使用 Executors,ThreadPoolExecutor,建立執行緒池,原始碼分析理解

搜雲庫技術團隊發表於2019-02-27

之前建立執行緒的時候都是用的 newCachedThreadPoo,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor 這四個方法。
當然 Executors 也是用不同的引數去 new ThreadPoolExecutor 實現的,本文先分析前四種執行緒建立方式,後在分析 new ThreadPoolExecutor 建立方式

使用 Executors 建立執行緒池

1.newFixedThreadPool()

由於使用了LinkedBlockingQueue所以maximumPoolSize沒用,當corePoolSize滿了之後就加入到LinkedBlockingQueue佇列中。
每當某個執行緒執行完成之後就從LinkedBlockingQueue佇列中取一個。
所以這個是建立固定大小的執行緒池。

原始碼分析

public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(
			nThreads,
			nThreads,
			0L,
			TimeUnit.MILLISECONDS,
			new LinkedBlockingQueue<Runnable>());
}
複製程式碼

2.newSingleThreadPool()

建立執行緒數為1的執行緒池,由於使用了LinkedBlockingQueue所以maximumPoolSize 沒用,corePoolSize為1表示執行緒數大小為1,滿了就放入佇列中,執行完了就從佇列取一個。

原始碼分析

public static ExecutorService newSingleThreadExecutor() {
	return new Executors.FinalizableDelegatedExecutorService
			(
					new ThreadPoolExecutor(
							1,
							1,
							0L,
							TimeUnit.MILLISECONDS,
							new LinkedBlockingQueue<Runnable>())
			);
}

public ThreadPoolExecutor(int corePoolSize,
						  int maximumPoolSize,
						  long keepAliveTime,
						  TimeUnit unit,
						  BlockingQueue<Runnable> workQueue) {
	this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
			Executors.defaultThreadFactory(), defaultHandler);
}
複製程式碼

3.newCachedThreadPool()

建立可緩衝的執行緒池。沒有大小限制。由於corePoolSize為0所以任務會放入SynchronousQueue佇列中,SynchronousQueue只能存放大小為1,所以會立刻新起執行緒,由於maxumumPoolSizeInteger.MAX_VALUE所以可以認為大小為2147483647。受記憶體大小限制。

原始碼分析

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

public ThreadPoolExecutor(int corePoolSize,
						  int maximumPoolSize,
						  long keepAliveTime,
						  TimeUnit unit,
						  BlockingQueue<Runnable> workQueue) {
	this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
			Executors.defaultThreadFactory(), defaultHandler);
}
複製程式碼

使用 ThreadPoolExecutor 建立執行緒池

原始碼分析 ,ThreadPoolExecutor 的建構函式

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 核心執行緒數大小,當執行緒數 < corePoolSize ,會建立執行緒執行 runnable

2、maximumPoolSize 最大執行緒數, 當執行緒數 >= corePoolSize的時候,會把 runnable 放入 workQueue中

3、keepAliveTime 保持存活時間,當執行緒數大於corePoolSize的空閒執行緒能保持的最大時間。

4、unit 時間單位

5、workQueue 儲存任務的阻塞佇列

6、threadFactory 建立執行緒的工廠

7、handler 拒絕策略

任務執行順序

1、當執行緒數小於 corePoolSize時,建立執行緒執行任務。

2、當執行緒數大於等於 corePoolSize並且 workQueue 沒有滿時,放入workQueue

3、執行緒數大於等於 corePoolSize並且當 workQueue 滿時,新任務新建執行緒執行,執行緒總數要小於 maximumPoolSize

4、當執行緒總數等於 maximumPoolSize 並且 workQueue 滿了的時候執行 handlerrejectedExecution。也就是拒絕策略。

四個拒絕策略

ThreadPoolExecutor預設有四個拒絕策略:

1、ThreadPoolExecutor.AbortPolicy() 直接丟擲異常RejectedExecutionException

2、ThreadPoolExecutor.CallerRunsPolicy() 直接呼叫run方法並且阻塞執行

3、ThreadPoolExecutor.DiscardPolicy() 直接丟棄後來的任務

4、ThreadPoolExecutor.DiscardOldestPolicy() 丟棄在佇列中隊首的任務

當然可以自己繼承RejectedExecutionHandler來寫拒絕策略.

TestThreadPoolExecutor 示例

TestThreadPoolExecutor.java

package io.ymq.thread.TestThreadPoolExecutor;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 描述:
 *
 * @author yanpenglei
 * @create 2017-10-12 15:39
 **/
public class TestThreadPoolExecutor {
    public static void main(String[] args) {

        long currentTimeMillis = System.currentTimeMillis();

        // 構造一個執行緒池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 6, 3,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3)
        );

        for (int i = 1; i <= 10; i++) {
            try {
                String task = "task=" + i;
                System.out.println("建立任務並提交到執行緒池中:" + task);
                threadPool.execute(new ThreadPoolTask(task));

                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        try {
            //等待所有執行緒執行完畢當前任務。
            threadPool.shutdown();

            boolean loop = true;
            do {
                //等待所有執行緒執行完畢當前任務結束
                loop = !threadPool.awaitTermination(2, TimeUnit.SECONDS);//等待2秒
            } while (loop);

            if (loop != true) {
                System.out.println("所有執行緒執行完畢");
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("耗時:" + (System.currentTimeMillis() - currentTimeMillis));
        }


    }
}

複製程式碼

ThreadPoolTask.java

package io.ymq.thread.TestThreadPoolExecutor;

import java.io.Serializable;

/**
 * 描述:
 *
 * @author yanpenglei
 * @create 2017-10-12 15:40
 **/
public class ThreadPoolTask implements Runnable, Serializable {

    private Object attachData;

    ThreadPoolTask(Object tasks) {
        this.attachData = tasks;
    }

    public void run() {

        try {

            System.out.println("開始執行任務:" + attachData + "任務,使用的執行緒池,執行緒名稱:" + Thread.currentThread().getName());

            System.out.println();

        } catch (Exception e) {
            e.printStackTrace();
        }
        attachData = null;
    }

}
複製程式碼

遇到java.util.concurrent.RejectedExecutionException

第一

你的執行緒池 ThreadPoolExecutor 顯示的 shutdown() 之後,再向執行緒池提交任務的時候。 如果你配置的拒絕策略是 AbortPolicy 的話,這個異常就會丟擲來。

第二

當你設定的任務快取佇列過小的時候,或者說, 你的執行緒池裡面所有的執行緒都在幹活(執行緒數== maxPoolSize),並且你的任務快取佇列也已經充滿了等待的佇列, 這個時候,你再向它提交任務,則會丟擲這個異常。

響應

可以看到執行緒 pool-1-thread-1 到5 迴圈使用

建立任務並提交到執行緒池中:task=1
開始執行任務:task=1任務,使用的執行緒池,執行緒名稱:pool-1-thread-1

建立任務並提交到執行緒池中:task=2
開始執行任務:task=2任務,使用的執行緒池,執行緒名稱:pool-1-thread-2

建立任務並提交到執行緒池中:task=3
開始執行任務:task=3任務,使用的執行緒池,執行緒名稱:pool-1-thread-3

建立任務並提交到執行緒池中:task=4
開始執行任務:task=4任務,使用的執行緒池,執行緒名稱:pool-1-thread-4

建立任務並提交到執行緒池中:task=5
開始執行任務:task=5任務,使用的執行緒池,執行緒名稱:pool-1-thread-5

建立任務並提交到執行緒池中:task=6
開始執行任務:task=6任務,使用的執行緒池,執行緒名稱:pool-1-thread-1

建立任務並提交到執行緒池中:task=7
開始執行任務:task=7任務,使用的執行緒池,執行緒名稱:pool-1-thread-2

建立任務並提交到執行緒池中:task=8
開始執行任務:task=8任務,使用的執行緒池,執行緒名稱:pool-1-thread-3

建立任務並提交到執行緒池中:task=9
開始執行任務:task=9任務,使用的執行緒池,執行緒名稱:pool-1-thread-4

建立任務並提交到執行緒池中:task=10
開始執行任務:task=10任務,使用的執行緒池,執行緒名稱:pool-1-thread-5

所有執行緒執行完畢
耗時:1015
複製程式碼

測試程式碼

github github.com/souyunku/ym…

Contact

  • 作者:鵬磊
  • 出處:www.ymq.io
  • Email:admin@souyunku.com
  • 版權歸作者所有,轉載請註明出處
  • Wechat:關注公眾號,搜雲庫,專注於開發技術的研究與知識分享
關注公眾號-搜雲庫
搜雲庫

相關文章