Thread pool execution principle

table of Contents

Basic introduction to thread pool

Thread pool parameters

Thread pool status and basic methods

The execution process of the thread pool

Built-in thread pool

newFixedThreadPool

newSingleThreadExecutor

newCachedThreadPool

newScheduledThreadPool


Basic introduction to thread pool

The appearance rate of the thread pool in the project is still relatively high. If the thread pool is introduced in a more vernacular language, it is a thread collection + task queue. When a task request arrives in the thread pool, it is stuffed into the task queue, and then A simple scenario where threads take tasks from the queue in an endless loop. So why do we need a thread pool?

  • Avoid frequent creation and destruction of threads and reduce resource consumption
  • Thread pool can better manage thread resources, such as allocation, tuning and monitoring
  • Improve the response speed, when the task comes, it can respond without waiting for the thread to be created

Thread pool parameters

In fact, jdk provides us with several built-in thread pools, but it is not recommended to use them. It is better to create them manually. The method parameters for creating thread pools are as follows:

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;
    }

The following describes these parameters in a little detail, and then analyzes the use of each parameter in combination with the source code of the execution process of the thread pool

int corePoolSize:线程池中的核心线程数量,即使没有任务,这些线程也不会销毁。
int maximumPoolSize:线程池中的最大线程数量,即线程池所支持创建的最大数量线程,即使任务超级多,也只会有 maximumPoolSize 数量的线程在运行。
long keepAliveTime:非核心线程的存活时间,当任务数量超过核心线程数量时,只要 corePoolSize < maximumPoolSize,线程池便会创建对应的线程数去执行任务,当线程池中存活的线程数量大于核心线程时,如果等了 keepAliveTime 时间仍然没有任务进来,则线程池会回收这些线程。
TimeUnit unit:非核心线程存活时间的具体单位,即等待多少毫秒、秒等。
BlockingQueue<Runnable> workQueue:存储线程任务所用的队列,提交的任务将会被放到该队列中。
ThreadFactory threadFactory:线程工厂,主要用来创建线程的时候给线程取名用,默认是pool-1-thread-3
RejectedExecutionHandler handler:线程拒绝策略,当存储任务所用的队列都被填满时,新来的任务此时无处存放,那么需要提供一种策略去解决这种情况。

Thread pool status and basic methods

This class defines the basic state and basic methods of some thread pools. You need to know about it before reading the source code:

/*
 * 该对象高3位维护线程池运行状态,低29位维护当前线程池数量
 */
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
/*
 * 该值等于29,用于左移
 */
private static final int COUNT_BITS = Integer.SIZE - 3;
/*
 * 线程池支持的最大线程数量,即29位所能表示的最大数值,2^29 - 1
 */
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
/*
 * 即高3位为111,低29位为0,该状态的线程池会接收新任务,并且处理阻塞队列中正在等待的任务
 */
private static final int RUNNING    = -1 << COUNT_BITS;
/*
 * 即高3位为000,不接受新任务,但仍会处理阻塞队列中正在等待的任务
 */
private static final int SHUTDOWN   =  0 << COUNT_BITS;
/*
 * 高3位为001,不接受新任务也不处理阻塞队列中的任务
 */
private static final int STOP       =  1 << COUNT_BITS;
/*
 * 高3位为010,所有任务都被终止了,workerCount为0,为此状态时还将调用terminated()方法
 */
private static final int TIDYING    =  2 << COUNT_BITS;
/*
 * 高3位为011,terminated()方法调用完成后变成此状态
 */
private static final int TERMINATED =  3 << COUNT_BITS;

These states are represented by the int type, and the size relationship is RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED, and this sequence basically follows the process of thread pool from running to terminating. The following three methods are commonly used in this class to obtain the thread pool status and the number of threads:

/*
 * c & 高3位为1,低29位为0的~CAPACITY,用于获取高3位保存的线程池状态
 */
private static int runStateOf(int c)     { return c & ~CAPACITY; }
/*
 * c & 高3位为0,低29位为1的CAPACITY,用于获取低29位的线程数量
 */
private static int workerCountOf(int c)  { return c & CAPACITY; }
/*
 * 参数rs表示runState,参数wc表示workerCount,即根据runState和workerCount打包合并成ctl
 */
private static int ctlOf(int rs, int wc) { return rs | wc; }

The execution process of the thread pool

The specific execution logic is mainly in the execute() method of ThreadPoolExecutor . The source code is as follows:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        // ctl对象功能很强大,其高3位代表线程池的状态,低29位代表线程池中的线程数量
        int c = ctl.get();
        // 如果当前线程池中线程数量小于核心线程数,则新建一个线程并将当前任务直接赋予该线程执行
        if (workerCountOf(c) < corePoolSize) {
            // 如果新建线程成功则直接返回
            if (addWorker(command, true))
                return;
            // 到这一步说明新建失败,可能是线程池意外关闭或者是由于并发的原因导致当前线程数大于等于核心线程数了,重新获取ctl对象
            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);
        }
        // 如果处于非运行态或者入队列不成功(队列满了),尝试扩容线程池线程数量至maxPoolSize,若扩容失败,则拒绝该任务
        else if (!addWorker(command, false))
            reject(command);
    }

To summarize briefly, it is mainly divided into three steps:

  1. First judge whether the number of threads currently running is less than the number of core threads, if so, create a new thread to process the task, and return directly if the processing is successful, otherwise continue to judge
  2. Determine whether the current thread pool is in the running state and whether the task is successfully queued, and if so, double-check to ensure that there are still running threads in the pool
  3. If the second step into the queue fails, the thread pool attempts to expand to the maximum number of threads, if it fails, the task is rejected

Since the above steps 1 and 3 need to be locked when adding threads, it will affect the performance, so most of the operations of the thread pool are executed in the second step (relying on the number of core threads to support) , Unless the blocking queue can no longer fit. It can be seen from the above code that even if the number of core threads is defined, the number of corePoolSize threads is not created in advance, but the lock is added each time. Therefore, for performance considerations, you can call prestartAllCoreThreads( ) Method to start the corePoolSize number of threads in advance.

Let's take a look at the method addWorker () to create a new thread 

private boolean addWorker(Runnable firstTask, boolean core) {
        // 首先是一个外层死循环
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 检查当前线程池是否处于非运行态,同时确保队列中任务数不为空
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
            // 内存死循环修改运行的线程数量
            for (;;) {
                int wc = workerCountOf(c);
                // core参数确保不会超过线程池设定的值
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 采用CAS算法将线程数+1,如果成功则直接跳出外循环,失败主要是因为并发修改导致,那么则再次内循环判断
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 确保线程池运行状态没变,若发生改变,则从外循环开始判断
                c = ctl.get(); 
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            // 新建Worker内部类时主要干了两件事,一个是设置AQS同步锁标识为-1,另一个是调用线程工厂创建线程并赋值给Worker的成员变量
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                // 在往workers这个线程集合增加线程时需要进行加锁
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // 此处需要进行二次检查,防止线程池被关闭等异常情况
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 如果当前线程已经启动,处于活跃态则抛异常
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        // workers是一个HashSet集合
                        workers.add(w);
                        int s = workers.size();
                        // 设置最大池大小,同时标识任务线程增加成功,即 workerAdded 设为true
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                // 如果任务线程成功增加,则在此处启动线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

It seems to be very long, but it is actually doing some basic judgments to prevent abnormal situations. A brief summary:

  1. First determine whether it is in the running state and the number of threads currently running is less than the upper limit, if so, the number of running threads is first added by the CAS algorithm
  2. After the CAS operation is successful, create a new Worker thread and add it to the thread collection by locking, and then start the thread

Next, look at the Worker internal class. This class wraps the task thread, mainly to control thread interruption, that is, when the thread pool is closed, the corresponding thread task needs to be interrupted. The interruption mentioned here is waiting to get the task from the workQueue getTask( ) Can only be interrupted, that is, the interruption is allowed after the thread actually starts running, so the lock state is a negative value (-1) during initialization.

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** 由线程工厂所创建的线程对象 */
        final Thread thread;
        /** 业务任务对象 */
        Runnable firstTask;
        /** 当前线程所执行任务的计数 */
        volatile long completedTasks;

        /**
         * 初始化方法,主要是设置AQS的同步状态private volatile int state,是一个计数器,大于0代表锁已经被获取,设为-1后即禁止中断
         */
        Worker(Runnable firstTask) {
            setState(-1); // 将lock标识设为-1,
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        public void run() {
            runWorker(this);
        }

        /**
         * 下面的都是与锁相关的方法,state为0代表锁未被获取,1代表锁已经被获取
         */
        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            // 控制中断主要就是体现在中断前会判断 getState() >= 0
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

The Worker class itself implements Runnable and inherits AbstractQueuedSynchronizer, so it is not only an executable task, but also the effect of a lock. Note that the lock is a simple non-reentrant lock

Finally, look at the method runWorker() that mainly performs business tasks in the thread pool:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        // 将state置为0,允许线程中断
        w.unlock(); 
        boolean completedAbruptly = true;
        try {
            // task为上层透传进来的指定业务线程,若为空则循环通过getTask()获取任务执行
            while (task != null || (task = getTask()) != null) {
                // 这里的加锁不是为了防止并发,而是为了在shutdown()时不终止正在运行的任务
                w.lock();
                // 双重检查防止线程池状态不大于stop且未被设为中断标识
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    // 在执行业务代码之前执行,由子类实现
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        // 在执行业务代码之后执行,由子类实现
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            // 处理业务线程抛异常的情况
            processWorkerExit(w, completedAbruptly);
        }
    }

 

Built-in thread pool

JDK has built-in four thread pools for us, all of which are created through the Executors factory class. It is not recommended to create thread pools in this way. The following will introduce why not.

newFixedThreadPool

This is a fixed-length thread pool

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

As the name implies, a fixed-length thread pool is created. The number of core threads in the thread pool is the same as the maximum number of threads. Therefore, the non-core thread survival time parameter is meaningless. The biggest problem is that the selected blocking queue is unbounded. Infinitely, it will keep adding and adding, eventually bursting the memory. It is generally used for servers with heavier loads and needs to limit the specified number of threads.

newSingleThreadExecutor

This is a single-threaded thread pool

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

Looking at the parameters, you can also find that only one thread is initialized, and there is only one thread working in the pool from beginning to end. It sounds really pitiful. The only problem is that the blocking queue is unbounded, which may burst the memory. It is generally used for strict requirements. Scenarios that control the order of task execution.

newCachedThreadPool

This is a cacheable thread pool

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

The number of core threads is 0, and the maximum number of threads tends to be infinite, that is, all non-core threads are used to process tasks, and the survival time of each thread is 60s. Note that a synchronous queue is used here, that is, only tasks will be delivered and will not be saved. Task, once there is a task, check if there is an idle thread, if not, create a new thread. When the rate of task request is greater than the rate of thread processing, the number of threads will increase and eventually the memory will burst. Generally used for concurrency Perform a large number of short-term small tasks, because the thread that is idle for 60 seconds will be recycled, so the thread pool that remains idle for a long time will not occupy any resources.

newScheduledThreadPool

This is also a fixed-length thread pool, but it supports timing and periodic task execution

public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory,
                                       RejectedExecutionHandler handler) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory, handler);
    }

It can be seen that the maximum number of threads is still infinite, so there will be a memory burst problem to a certain extent. In the use of the queue, the delayed blocking queue DelayedWorkQueue is selected, and the specific task execution is also more complicated.

 

Guess you like

Origin blog.csdn.net/m0_38001814/article/details/107729317