任务加入线程池是的生效和启动过程详解

前言

很早之前我就写过两篇关于线程池的博客, 但是这两篇博客的认识比较浅陋, 有很多需要订正的地方, 所以我决定新开一篇博客.
https://blog.csdn.net/leisurelen/article/details/107872827
https://blog.csdn.net/leisurelen/article/details/107872827
在这里我先简单描述下线程池的工作流程

  1. 加入一个Task, 如果没有达到核心线程数, 启动一个新Worker执行这个Task
  2. 再加入一个Task, 检查已经达到核心线程数量, 直接把这个Task放入到任务队列中.
  3. 第2步中放入任务队列成功, 会在次检查线程池的状态和运行线程的数量, 并根据状态等维护线程数
  4. 在放入一个Task, 检查已经达到核心线程数量, 直接放入任务队列, 放入任务队列失败. 尝试启动Worker来运行Task
  5. 第4步中启动一个新Worker中. 在worker中会检查线程池状态, 最大线程数是否超标, 如果状态不符合就会return false.
  6. 第5步中返回了false , 就会执行拒绝策略

线程池的定义

线程池有7个参数, 每个参数都有各自的作用.

corePoolSize: 核心线程数

其实按照代码的逻辑, 这个也可以叫做 正常工作线程数 , 线程池启动工作后, 首先 也就是在线程池的任务队列够用的时候, 会始终以这个数量的线程工作.

maximumPoolSize : 最大线程数

当任务队列不够用的时候, 会继续增加超出 corePoolSize的数量的线程, 但是最终线程不会超出maximumPoolSize数量. 设置这个值可以防止任务太多而创建太多的线程

keepAliveTime 和 unit 线程的存活时间

当任务较少的时候线程空闲的时候, 超出核心线程数量的线程会存活的时间, 超出这个时间后会自动关闭多余的线程.

workQueue: 线程池的任务队列

线程池往往是用来批量处理任务的, 当任务过多来不及处理的时候, 会将任务放入这个队列中缓存, 避免阻塞线程池的调用方法

threadFactory : 创建线程的工厂, 可以自定义线程名称

线程池中的线程的创建都是通过这个工厂的, 在这里可以定义线程的名称, 方便在线程出现问题的时候在错误日志中一眼就能看到具体的线程.

RejectedExecutionHandler: 线程池的拒绝策略

这个是最容易被忽略的方法, 当线程达到上限, 并且任务队列也满了的时候, 如果继续往里面添加任务, 就会直接执行拒绝策略中的代码. 默认的拒绝策略是 直接抛出异常 .

如果线程池的状态是shutdown , 加入任务也会执行拒绝策略

    public static void main(String[] args) {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
                20,
                1,
                TimeUnit.MINUTES,
                new SynchronousQueue<>(),
                new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 30; i++) {
            threadPoolExecutor.execute(()->{
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }

加入任务的代码解析

代码的每一行我都加上了注释, 看注释就能明白.
简单总结下流程就是:

  1. 先启动核心线程执行任务, 直到达到核心线程数
  2. 在把任务放入缓存任务队列
  3. 缓存任务队列放满后, 再次启动线程来执行任务
  4. 在每次启动线程的时候都会检查状态和总线程数, 启动失败都会执行拒绝策略
/**
     * 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:
         * 流程分3步
         *
         * 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.
  
         * 1. 如果正在运行的线程数少于核心线程数, 会尝试启动一个新线程来运行给定的任务, 
         * 调用addWorker方法会先检查运行状态和运行的线程数了, 如果状态不对会返回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) {
            //调用addWorker新增线程,执行任务
            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) 
            	//检查运行的线程数, 如果为0,  会直接启动一个线程
                addWorker(null, false);
        }
        //如果非运行状态, 或者放入任务队列失败, 会尝试启动一个新的线程来运行任务
        else if (!addWorker(command, false))
        	//启动新的线程运行任务失败, 执行拒绝策略
            reject(command);
    }

addWorker 代码解析

内部代码分上下两块,

  • 第一个模块, 主要是循环检查线程池的状态, 并CAS修改线程池的状态, 修改成功后跳出循环, 修改失败后重试或者返回false
  • 第二个模块流程简单多了, 主要是新建一个worker , 并加锁后 把worker 加入到workers中, 更新线程数量, workers是池内部维护的一个状worker的hashSet . 最后在启动worker中的线程.

可以看到, 加入任务的两个模块都没有直接涉及到任务队列的交互, 只是简单的判断状态, 创建线程启动等, 更重要的逻辑其实在Worker对象内部,

/**
     * 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.
            //检查线程池的运行状态是否shutdown, 任务队列是否为空等状态是否正常,
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty())
               )
                return false;
			//循环更新状态, 和线程数量的更新, 都是使用CAS的模式更新
            for (;;) {
                int wc = workerCountOf(c);
                //检查运行的线程数是否超标,
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //CAS方式更新线程数量
                if (compareAndIncrementWorkerCount(c))
                    //更新成功后直接跳转到方法第一行, 并且不在进入这些for循环中, 
                    //因为状态啥的已经更新成功了
                    break retry;
                c = ctl.get();  // Re-read ctl
                //如果cas增加线程数失败, 检查下状态是否还是和之前一样, 
                if (runStateOf(c) != rs)
                	//不一样了, 会跳转到第一行, 并会重新进入for循环中走一遍流程
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }


		// -------------------------------------------------------------------//

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
        	//新建一个worker, 内部包含线程
            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();
                        //worker放入池的hashset中
                        workers.add(w);
                        int s = workers.size();
                        //并将workers的数量赋予池中的largestPoolSize成员变量
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                //worker创建成功并加入workers成功, 
                if (workerAdded) {
                    //启动线程, 
                    t.start();
                    // 修改状态
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                 //启动线程失败, 进行失败处理
                addWorkerFailed(w);
        }
        return workerStarted;
    }

Worker对象源码解析

  1. 继承AbstractQueuedSynchronizer类, 说明worker 本身会实现有一定的锁的能力, worker自己实现了lock和acquire等锁的方法
  2. 实现Runable并且构造方法中接受Runable, 这个就是典型的静态代理模式, worker 会代理传入的runable
 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;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
           // 直接设置AQS的状态,  
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }
        
        //下面的省略

}

worker 的runWorker 方法

  1. 整个方法流程走下来, 线程在空闲的时候都是在unlock的锁定状态, 只有在执行任务的时候, 会解除锁定,
  2. 这个方法在任务的执行前后加入了两个空实现方法, 方法继承扩展
  3. 可以看到代码中到处都有检查线程池状态的代码, 在任务执行前, 执行后都有.
/**
     * Main worker run loop.  Repeatedly gets tasks from queue and
     * executes them, while coping with a number of issues:
     *
     * 1. We may start out with an initial task, in which case we
     * don't need to get the first one. Otherwise, as long as pool is
     * running, we get tasks from getTask. If it returns null then the
     * worker exits due to changed pool state or configuration
     * parameters.  Other exits result from exception throws in
     * external code, in which case completedAbruptly holds, which
     * usually leads processWorkerExit to replace this thread.
     *
     * 2. Before running any task, the lock is acquired to prevent
     * other pool interrupts while the task is executing, and then we
     * ensure that unless pool is stopping, this thread does not have
     * its interrupt set.
     *
     * 3. Each task run is preceded by a call to beforeExecute, which
     * might throw an exception, in which case we cause thread to die
     * (breaking loop with completedAbruptly true) without processing
     * the task.
     *
     * 4. Assuming beforeExecute completes normally, we run the task,
     * gathering any of its thrown exceptions to send to afterExecute.
     * We separately handle RuntimeException, Error (both of which the
     * specs guarantee that we trap) and arbitrary Throwables.
     * Because we cannot rethrow Throwables within Runnable.run, we
     * wrap them within Errors on the way out (to the thread's
     * UncaughtExceptionHandler).  Any thrown exception also
     * conservatively causes thread to die.
     *
     * 5. After task.run completes, we call afterExecute, which may
     * also throw an exception, which will also cause thread to
     * die. According to JLS Sec 14.20, this exception is the one that
     * will be in effect even if task.run throws.
     *
     * The net effect of the exception mechanics is that afterExecute
     * and the thread's UncaughtExceptionHandler have as accurate
     * information as we can provide about any problems encountered by
     * user code.
     *
     * @param w the worker
     */
    final void runWorker(Worker w) {
    	//获取当前线程
        Thread wt = Thread.currentThread();
        //获取当前任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        //先unlock , 同样也可以加锁, 别的线程同样无法占用资源.  具体逻辑可以看下java的锁逻辑
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        	//while中判断task是否为空, 如果为空则从getTask()中取任务, getTask就是从任务队列中取任务. 
        	// 没取到任务, 不会进入循环, 也就还是占用着锁
            while (task != null || (task = getTask()) != null) {
            	//取到任务, 取消锁占用
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                //接下来检查线程的状态等等, 如果线程状态是停止中, 则打断当前线程执行
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                     // 这是一个空实现, 并且是protect的方法,  可以继承实现这个方法, 从而在线程池任务执行前加上自己的逻辑
                     //注意, 如果这里抛出异常, 会导致下面的业务run方法无法执行, 也是一种拦截线程池执行的方式
                    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 {
                        //也是空实现, 并且是protect的方法,  可以继承实现这个方法, 从而在线程池任务执行结束后加上自己的逻辑
                        afterExecute(task, thrown);
                    }
                } finally {
                    // 把当前处理完的任务设置成null , 方便垃圾回收
                    task = null;
                    // 计数加1
                    w.completedTasks++;
                    // 继续调用unlock方法, 加上锁, 这会使得线程在空闲状态下都是锁定状态
                    w.unlock();
                }
            }
            //处理完成后回复标识
            completedAbruptly = false;
        } finally {
            //处理线程的退出逻辑, allowCoreThreadTimeOut 内部会根据这个变量值来判断是否保持核心线程数量 
            processWorkerExit(w, completedAbruptly);
        }
    }

AbstractExecutorService#submit()

父类中的submit方法支持接受Callable方法, 并且返回一个future.

  1. 用户传入的一个Callable 只是一个有返回值的方法, 线程池执行的并不是这个方法, 而是包装后的FutureTask的run方法
  2. 对于线程池来说执行的还是没有返回值的run方法, 在run的内部会执行call方法, 并把返回值放入Future逻辑中, 实现异步返回逻辑
    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
FutureTask 的run方法
public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

总结

看完整个流程, 知道一个任务加入线程池, 线程池是怎样新建线程, 怎样执行任务的,

但是还是有一个疑惑, 线程池没有任务的时候, 线程是如何保持存活的, 非核心线程是如何保证存活一段时间后关闭的?

答案: 在下图中, 这个while循环在getTask能持续取得任务的时候, 这个线程不会结束, 如果没有任务后, 线程就会结束; 但是还是没有对核心线程和非核心线程进行区分对待. 其实逻辑是在getTask中, 如果是核心线程getTask会永远阻塞的, 非核心线程才会阻塞一段时间后返回null . 返回null 这个循环就会结束, 线程就会结束, 具体代码在第二张图中
任务加入线程池是的生效和启动过程详解_第1张图片

任务加入线程池是的生效和启动过程详解_第2张图片

你可能感兴趣的:(java,java,开发语言,后端)