线程池构造函数各参数说明、参数间逻辑流程图

目录

阿里巴巴推荐用以下方式来创建线程池

创建调度线程池ScheduledExecutorService

创建公共线程池ExecutorService

直接在spring配置文件中配置线程池

线程池构造函数各参数说明

corePoolSize(数据类型int)

maximumPoolSize(数据类型int)

 keepAliveTime(数据类型long)与unit(数据类型TimeUnit)

 workQueue(数据类型BlockingQueue)

threadFactory(数据类型ThreadFactory )

handler(数据类型RejectedExecutionHandler)

线程池各参数关系流程图


谁又能生而知之呢?谁都是从错到对,从不会到会。


并发工具包与线程连接池中介绍了如何创建线程池,也给出了阿里巴巴的创建线程池规范,让我们先回顾一下。

阿里巴巴推荐用以下方式来创建线程池

提示:这里只给出阿里巴巴推荐的创建线程池的方式,至于其它创建线程池的方式可详见并发工具包与线程连接池

创建调度线程池ScheduledExecutorService

需要引入依赖:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>

提示:SpringCloud项目,zuul等模块儿本身就依赖有commons-lang3依赖。

注:如果不想引入commons-lang3依赖,那么可以使用默认的线程工厂或者其它的线程工厂实现。

创建:

// 需要引入commons-lang3依赖
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory
                                                                              .Builder()
                                                                              .namingPattern("example-schedule-pool-%d")
                                                                              .daemon(true)
                                                                              .build());

创建公共线程池ExecutorService

需要引入依赖:

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>27.0.1-jre</version>
</dependency>

提示:SpringCloud项目,eureka、zuul等模块儿本身就依赖有guava依赖。

注:如果不想引入guava依赖,那么可以使用默认的线程工厂或者其它的线程工厂实现。

创建:

// ThreadFactoryBuilder需要引入guava依赖
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
// 创建Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 50, 3000L, TimeUnit.MILLISECONDS,
                                              new LinkedBlockingQueue<Runnable>(20), 
                                              namedThreadFactory, 
                                              new ThreadPoolExecutor.AbortPolicy());
// 执行任务
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown

使用示例:

/**
 * 虽然我们可以通过Excutors创建线程池,但是推荐:我们自己手动创建线程池
 *
 * @author JustryDeng
 * @date 2018/12/29 13:58
 */
public class CreateThreadPool {

    private static final Object OBJ = new Object();

    private static Integer count = 10000;

    public static void main(String[] args) {
        try {
            createThreadPoolTest();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void createThreadPoolTest() throws InterruptedException {
        int length = 10000;

        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("justry-deng-pool-%d").build();

        ExecutorService executorService = new ThreadPoolExecutor(5, 50,
                                                                 2000L, TimeUnit.MILLISECONDS,
                                                                 new LinkedBlockingQueue<>(10),
                                                                 namedThreadFactory,
                                                                 new ThreadPoolExecutor.AbortPolicy());
        for (int i = 1; i <= length; i++) {
            // 使用lambel表达式简单实现Runnable接口的run方法
            executorService.execute(() -> {
                // 只有获得了object的锁的线程,才能操作
                synchronized (OBJ) {
                    System.out.println(Thread.currentThread().getName());
                    count--;
                }
            });
        }
        // 当线程池中所有线程都运行完毕后,关闭线程池
        executorService.shutdown();
        // 主线程阻塞2秒再输出count的值,为了避免输出打印count的值时,其余线程还没计算完;导致输出的不是count的最终值
        Thread.sleep(2000);
        System.out.println(count);
    }

}

直接在spring配置文件中配置线程池

<bean id="userThreadPool"
    class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="10" />
    <property name="maxPoolSize" value="100" />
    <property name="queueCapacity" value="2000" />
    <property name="threadFactory" value= threadFactory />
    <property name="rejectedExecutionHandler">
        <ref local="rejectedExecutionHandler" />
    </property>
</bean>

线程池构造函数各参数说明

在回顾完阿里巴巴推荐如何创建线程池后,让我们再来看看各参数说明:

corePoolSize(数据类型int)

          线程池的核心线程数。

注:当程序通过ExecutorService.execute(Runnable)或ExecutorService.submit(Callable)向线程池提交了新
        的线程任务请求时,若已创建的core线程数少于corePoolSize时,(即使存在空闲的core线程,也)会创
        建一个新的core线程来处理该请求。

注:当线程任务请求少于核心线程数时,(除非设置了allowCoreThreadTimeOut,否者)多出来的核心线程数
       也不会被回收。
       追注:若设置了allowCoreThreadTimeOut为true;那么当core线程空闲时间超过keepAliveTime时间后,
                  该core线程会被回收。

maximumPoolSize(数据类型int)

       最大线程数。当core线程被线程任务占满时,若此时有新的线程任务进来,那么新的线程任务会被排进BlockingQueue<Runnable>队列中,若core线程被线程任务占满并且BlockingQueue<Runnable>队列也被线程排队排满了时。会判断maximumPoolSize值是否大于corePoolSize,如果maximumPoolSize不大于corePoolSize,那么线程池会按(RejectedExecutionHandler)策略拒绝执行该线程任务;如果maximumPoolSize大于corePoolSize,那么会创建新的线程。当线程任务少于core线程时,非core线程会被回收。

注:使用时maximumPoolSize应不小于corePoolSize(虽然理论上可以小于)。

注:创建的总线程数不能多于maximumPoolSize,若有多于maximumPoolSize的线程任务的话,那么会
       按(RejectedExecutionHandler)策略拒绝执行那些多出来的线程任务。

keepAliveTime(数据类型long)unit(数据类型TimeUnit)

       设置线程超时时间。若某个非core线程的空闲时间超过了keepAliveTime的话,那么该线程会被回收;若某个core线程的空闲时间超过了keepAliveTime并且设置了allowCoreThreadTimeOut=true,那么该core线程也会被回收。

workQueue(数据类型BlockingQueue<Runnable>)

       等待队列。当core线程满了时,如有新的线程任务进来,那么会优先考虑处放入此等待队列中;若此等待队列也满了,那么才会通过判断maximumPoolSize是否比corePoolSize大,来决定是否创建新的线程还是拒绝该线程任务了。

注:BlockingQueue<Runnable>的默认实现有以下几种:

  • SynchronousQueue

  • ArrayBlockingQueue

  • DelayQueue

  • LinkedBlockingDeque

  • LinkedBlockingQueue

  • LinkedTransferQueue

  • PriorityBlockingQueue

提示关于这几种队列的详细介绍以及测试用例,会在下一篇博客中给出。

threadFactory(数据类型ThreadFactory )

       线程工厂。线程池利用该工厂创建线程。

注:java.util.concurrent包下的Executors类中提供有一个ThreadFactory 接口的默认实现内部类DefaultThreadFactory。

注:很多jar包也提供有自己对ThreadFactory 接口的实现。

handler(数据类型RejectedExecutionHandler)

       线程任务拒绝策略。

注:RejectedExecutionHandler 的默认实现有以下四种

  • ThreadPoolExecutor.AbortPolicy抛出RejectedExecutionException异常。

  • ThreadPoolExecutor.CallerRunsPolicy直接在原(调用ExecutorService.execute(Runnable)方法
                                                                         原调用ExecutorService.submit(Callable)方法的
    )线程中去
                                                                         运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。

  • ThreadPoolExecutor.DiscardOldestPolicy它将放弃最旧的那一个线程任务请求,然后重试execute;
                                                                               如果执行程序已关闭,则会丢弃该任务。

  • ThreadPoolExecutor.DiscardPolicy默认情况下它将丢弃被拒绝的任务。

线程池各参数关系流程图

声明:下述流程图整理自ThreadPoolExecutor.execute方法、ThreadPoolExecutor.addWorker方法、
            ArrayBlockingQueue<E>.offer方法。

提示:不同的队列对offer方法的实现各有不同,但核心仍然是:将线程任务放入队列。

给出上述流程图相关核心方法:

提示:以下源码来自jdk1.8。

ThreadPoolExecutor.execute方法:

/**
 * Executes the given task sometime in the future.  The task
 * may execute in a new thread or in an existing pooled thread.
 *
 * If the task cannot be submitted for execution, either because this
 * executor has been shutdown or because its capacity has been reached,
 * the task is handled by the current {@code RejectedExecutionHandler}.
 *
 * @param command the task to execute
 * @throws RejectedExecutionException at discretion of
 *         {@code RejectedExecutionHandler}, if the task
 *         cannot be accepted for execution
 * @throws NullPointerException if {@code command} is null
 */
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

ThreadPoolExecutor.addWorker方法:

/**
 * Checks if a new worker can be added with respect to current
 * pool state and the given bound (either core or maximum). If so,
 * the worker count is adjusted accordingly, and, if possible, a
 * new worker is created and started, running firstTask as its
 * first task. This method returns false if the pool is stopped or
 * eligible to shut down. It also returns false if the thread
 * factory fails to create a thread when asked.  If the thread
 * creation fails, either due to the thread factory returning
 * null, or due to an exception (typically OutOfMemoryError in
 * Thread.start()), we roll back cleanly.
 *
 * @param firstTask the task the new thread should run first (or
 * null if none). Workers are created with an initial first task
 * (in method execute()) to bypass queuing when there are fewer
 * than corePoolSize threads (in which case we always start one),
 * or when the queue is full (in which case we must bypass queue).
 * Initially idle threads are usually created via
 * prestartCoreThread or to replace other dying workers.
 *
 * @param core if true use corePoolSize as bound, else
 * maximumPoolSize. (A boolean indicator is used here rather than a
 * value to ensure reads of fresh values after checking other pool
 * state).
 * @return true if successful
 */
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

 ArrayBlockingQueue<E>.offer方法:

/**
 * Inserts the specified element at the tail of this queue if it is
 * possible to do so immediately without exceeding the queue's capacity,
 * returning {@code true} upon success and {@code false} if this queue
 * is full.  This method is generally preferable to method {@link #add},
 * which can fail to insert an element only by throwing an exception.
 *
 * @throws NullPointerException if the specified element is null
 */
public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length)
            return false;
        else {
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}

^_^ 如有不当之处,欢迎指正

^_^ 本文已经被收录进《程序员成长笔记(三)》,笔者JustryDeng

猜你喜欢

转载自blog.csdn.net/justry_deng/article/details/89331199