线程池系列之线程池的创建

425 阅读4分钟

1.线程池执行过程

这里贴出ThreadPoolExecutor(Runnble command)源码,源码解析参考了这个博客点我和自己的理解:

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.
         */
        //  获取当前线程池的状态以及线程池中线程数量,高3位表示线程池状态,后面的位数表示线程个数
        int c = ctl.get();
        // 如果线程池的线程数量 < 核心线程数
        if (workerCountOf(c) < corePoolSize) {
            // 添加一个核心线程执行任务,true 表示为核心线程
            if (addWorker(command, true))
                return;
            // 再次获取线程池状态和线程池中线程数量,防止并发的执行execute方法导致的线程池发生变化
            c = ctl.get();
        }
        // 如果if语句要返回ture,需要满足两个条件
        // 1.线程池处于RUNNING状态(只有处于此状态,才能接受新的任务)
        // 2.添加到任务队列成功
        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);
        }
        // 如果队列也满了,就创建一个非核心线程(core==false)执行任务
        else if (!addWorker(command, false))
            // 创建非核心线程执行任务失败,执行拒绝策略
            reject(command);
    }
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        // 双层死循环的第一层循环
        for (;;) {
            int c = ctl.get();
            // 获取当前线程池状态
            int rs = runStateOf(c);
            // 这一行代码看起来有点难以理解,返回 false 表示无法接收或处理任务
            // 如果不想返回 false,也就是 if 语句判断不成立,需要满足以下两个条件之一:
            // 1. rs < SHUTDOWN, 也就是 rs == RUNNING(我们之前讲过 RUNNING 表示可以接受新的任务)
            // 2. rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()
            // 	 2.1 rs == SHUTDOWN 表示不会接收新的任务,但是会执行阻塞队列中的任务
            // 	 2.2 firstTask == nul 表示要执行阻塞队列中的任务
            // 	 2.3 阻塞队列不能为空
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
            // 双层死循环的第二层循环
            for (;;) {
                // 获取当前工作线程的数量
                int wc = workerCountOf(c);
                // 返回false符合下面条件之一
                // 1.当前工作线程的数量 > 最大容量(CAPACITY)
                // 2.如果core=true,则当前工作线程的数 > 核心线程数时,返回false;
                //   如果core=fasle,则当前工作线程数 > 最大线程数,返回false
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 通过CAS(自旋)操作, 把线程池中的线程数量+1,然后跳出双层循环,进入第二部分
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                // CAS操作失败,就核对线程池的状态(因为ctl是共享变量,同时执行addWorker方法时,CAS不一定成功)
                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 {
            // 包装需要执行任务(注意 firstTask 可能为 null)
            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 集合
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        // 添加成功
                        workerAdded = true;
                    }
                } finally {
                    // 释放锁
                    mainLock.unlock();
                }
                if (workerAdded) {
                    // 启动线程
                    t.start();
                    // 启动成功
                    workerStarted = true;
                }
            }
        } finally {
            // 如果线程启动失败, 就删除 workers 集合中刚添加的 Worker 对象
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

2.线程池创建的几种方式

2.1使用Executors线程池工具类(相当于工厂类)创建线程池

a)Executors.newCachedThreadPool();

创建可缓存的线程池

b)Executors.newFixedThreadPool();

创建固定数目的线程池

c)Executors.newSingleThreadPoolExecutor();

创建单线程化线程池

d)Executors.newScheduledThreadPool();

创建一个支持定时及周期性执行任务的线程池

2.2手动创建线程池

通过构造函数创建线程池

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
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;
    }

各个参数的含义:

  • corePoolSize:核心线程池大小;
  • maximumPoolSize:最大线程数;
  • keepAliveTime:超出核心线程数量的其余线程,如果空闲时间超过keepAliveTime,就会被退出(比如核心线程数10,最大线程数30,当线程池中线程数达到30时,可能新创建的这20个线程相当于是“借”的,如果这20个线程空闲时间超过keepAliveTime,就会被退出);
  • unit:keepAliveTime的时间单位;
  • workQueue:阻塞队列,会在其它系列文章会详细讲解
  • threadFactory:创建线程的工程类。如果项目中有多个线程池,可以通过指定线程工厂为每个创建出来的线程设置更有意义的名字。如果出现并发问题,也方便查找问题原因;
  • handler:饱和策略。当线程池的阻塞队列已满和指定的线程都已经开启,说明当前线程池已经处于饱和状态了,那么就需要采用一种策略来处理这种情况。采用的策略有这几种:
    • AbortPolicy: 直接拒绝所提交的任务,并抛出RejectedExecutionException异常;
    • CallerRunsPolicy:只用调用者所在的线程来执行任务 具体理解请参考
    • DiscardPolicy:不处理直接丢弃掉任务;
    • DiscardOldestPolicy:丢弃掉阻塞队列中存放时间最久的任务,执行当前任务

例如:

ExecutorService threadPool = new ThreadPoolExecutor(2,5,
        1L, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(3),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy());

提供一个手动创建线程池的工具类

  1. 导入guava的jar包
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>18.0</version>
    </dependency>
    
  2. 工具类ThreadPoolUtil
    public class ThreadPoolUtil {
        public static ExecutorService createThreadPool(String threadPoolName, int corePoolSize, int maximumPoolSize, int queueSize) {
            ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat(threadPoolName + "-%d").build();
            ExecutorService pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>(queueSize), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
            return pool;
        }
    }
    

3.Spring框架对线程池的管理

Spring中ThreadPoolTaskExecutor类对ThreadPoolExecutor类进行了封装,通过注册ThreadPoolTaskExecutor的bean来实现线程池的创建

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <!-- 核心线程池数量 -->
    <property name="corePoolSize" value="2"></property>
    <!-- 允许空闲时间,注意时间的单位是秒 -->
    <property name="keepAliveSeconds" value="200"></property>
    <!-- 线程池维护线程的最大数量 -->
    <property name="maxPoolSize" value="5"></property>
    <!-- 缓存队列的大小 -->
    <!-- queueCapacity=0,则创建SynchronousQueue缓存队列 -->
    <!-- queueCapacity>0,则创建LinkedBlockingQueue缓存队列 -->
    <property name="queueCapacity" value="1"></property>
    <!-- 对拒绝task的处理策略 -->
    <property name="rejectedExecutionHandler">
        <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
    </property>
</bean>
protected BlockingQueue<Runnable> createQueue(int queueCapacity) {
    return (BlockingQueue)(queueCapacity > 0 ? new LinkedBlockingQueue(queueCapacity) : new SynchronousQueue());
}   

4.SpringBoot对线程池的管理

@Configuration
public class AsyncConfiguration {
    @Bean("test1-executor")
    public ThreadPoolExecutor asyncExecutor() {
        int cpu = Runtime.getRuntime().availableProcessors();
        // 根据需求设置缓存队列的大小
        return new ThreadPoolExecutor(cpu, cpu << 2, 0L, TimeUnit.MILLISECONDS, new LinkedTransferQueue<>());
    }

    @Bean("test2-executor")
    public ThreadPoolExecutor redisExecutor() {
        // 根据需求设置缓存队列的大小
        return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedTransferQueue<>());
    }

}

注入时参考如下:

@Component
public class Test1 {
    @Autowired
    @Qualifier("test1-executor")
    ThreadPoolExecutor threadPool;
}

@Component
public class Test2 {
    @Autowired
    @Qualifier("test2-executor")
    ThreadPoolExecutor threadPool;
}

5.为什么不推荐通过Executors直接创建线程池

根据上面的总结,我们可以发现,使用Executors的方法创建线程池是比较简单,那么为什么阿里巴巴java开发手册中,明确指出,不允许使用Executors创建线程池。

4. 【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 
说明:Executors返回的线程池对象的弊端如下: 
    1) FixedThreadPool和SingleThreadPool: 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。 
    2) CachedThreadPool: 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

分析如下:

  1. newFixedThreadPoolnewSingleThreadExecutor创建线程池时使用的阻塞队列是LinkedBlockingQueue,并且未指定容量。此时,LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出的问题。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory)
}
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}
  1. newCachedThreadPool允许创建的非核心线程数量是Integer.MAX_VALUE,可能会导致创建大量的线程,从而导致OOM
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

6.参考

  1. www.jianshu.com/p/125ccf004…
  2. www.cnblogs.com/vwvwvwgwg/p…