【JUC 并发编程】— 线程池(下)

构造方法

创建线程池的方法如下:

ExecutorService executor = Executors.newFixedThreadPool(8);

但是 《阿里 Java 开发手册》中禁止使用Executors工具类的方式创建线程池,而是推荐使用ThreadPoolExecutor的构造方法直接创建。ThreadPoolExecutor构造方法如下:

/**
 * 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) {
}

可以看到核心构造方法有 7 个参数,意义如下:

  • corePoolSize:线程池核心线程数。
  • maximumPoolSize:线程池中允许最大的线程数。
  • keepAliveTime:如果线程池中线程数大于 corePoolSize,多余的线程空闲时间,过了该时间,线程将终止。
  • unit:keepAliveTime 时间单位,不管定义的时候是什么单位,最后都会转换成纳秒。
  • workQueue:任务队列。
  • threadFactory:线程工厂,用来创建线程池中线程。
  • handler:当队列满了,将采用的拒绝策略拒绝任务。

重要属性

线程池中有几个比较重要的属性,在深入源码前先混个眼熟

ctl

源码中是这么定义的:The main pool control state, ctl, is an atomic integer packing two conceptual fields,意思是说:线程池的状态变量,AtomicInteger 类型的,主要包装了两个概念上的属性

  • workerCount:线程池中线程的数量
  • runState:线程池的状态

ctl 定义:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

通过 ctl 可以获取到workerCountrunState,如下

// Packing and unpacking ctl
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

线程池状态

线程池中定义了下面这几种状态

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS; // 接受新任务,处理队列中的任务
private static final int SHUTDOWN   =  0 << COUNT_BITS; // 不接受新任务,但处理队列中的任务
private static final int STOP       =  1 << COUNT_BITS; // 不接受新任务,也不处理队列中的任务,还要中断进行中(in-progress)的任务
private static final int TIDYING    =  2 << COUNT_BITS; // 此时所有的任务终止,线程数为0,这个状态只是调用 terminated() 方法前的过渡状态
private static final int TERMINATED =  3 << COUNT_BITS; // terminated() 方法调用完成,线程池终止

可以看到线程池的这几种状态都是数字类型的,而且这些数字的顺序很重要,线程池状态随着时间推移,只会单调递增,也就说状态只会从 RUNNING 流转到 TERMINATED,而不能反过来。

源码分析

代码中使用线程池如下:

int processorCount = Runtime.getRuntime().availableProcessors();
// 这里请忽略创建方式
ExecutorService executor = Executors.newFixedThreadPool(processorCount);
threadPool.execute(new Runnable() {
	@Override
	public void run() {
		// do sth
	}
});

execute()

execute() 方法代码如下:

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) {
        // 则调用 addWorker() 方法创建线程,并处理任务
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 到这里,说明线程数 == 核心线程数,
    // 则调用 workQueue.offer(command) 入队
    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);
    }
    
    // 到这里,说明队列也满了
    // 如果 addWorker() 返回 false,说明线程数已经达到了最大线程数(maximumPoolSize),
    // 则拒绝这个任务
    else if (!addWorker(command, false))
        reject(command);
}

注释中说明了execute方法的主要逻辑:

  • 1、如果线程池中线程数小于核心线程数,则直接新建线程来处理这个任务;
  • 2、如果线程池中线程数等于核心线程数,则将任务加入队列(这里面有个 double-check,防止线程池状态变动);
  • 3、如果加入队列失败(说明队列满了),则新建线程来处理任务;
  • 3.1、如果新建线程失败(说明线程池线程数达到了最大线程数),则拒绝这个任务;
  • 3.2、新建线程成功,直接处理这个任务;

addWorker()

首先看上面代码中第一个 if 逻辑:如果线程池中线程数小于核心线程数,就调用addWorker()方法新增线程。addWorker()代码如下:

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(此状态下,线程池不接受新任务,只处理队列中剩余的任务),
        // 并且任务队列为空,说明线程池中已经没任务要处理,那还新建啥线程。。。直接返回 false
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c); // 获取线程池中线程数量
            // 1、线程数大于CAPACITY(线程池中最大限度的线程数量,maximumPoolSize 也必须小于该值)
            // 或者
            // 2.1、如果该方法是在线程数小于核心线程数时调用(core 为 true),
            //      并且现在线程数大于等于核心线程数(说明这期间有新的线程被创建,导致线程数达到了核心线程数),
            //      那直接返回false,因为这时候任务需要进入队列。
            // 2.2、如果线程数达到了最大线程数,那更不用创建了,直接返回false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
                
            // 到这里,说明可以创建线程了,通过 cas 并发更新线程数
            // 更新成功,则跳出该循环,否则就继续重试
            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 {
        // 线程池中线程都被包装成 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();
                    // 添加到 workers 中(HashSet)
                    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;
}

addWorker()在创建线程前,需要检查线程池的状态以及池中线程的数量等,只有符合条件才开始创建线程。线程被包装成Worker,这就很形象了。创建成功后,调用线程的start()方法启动线程,开始执行firstTask(如果不为 null 的话)的 run 方法中的逻辑。

Worker

这里补充一点线程的知识,我们都知道线程启动后就开始执行 run 方法中的代码了,如果你看了 run 方法的源码,就该明白其实执行的是Runnable中 run 方法的内容:

/* What will be run. */
private Runnable target;

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

如果按照这个思路,addWorker() 中最后创建的线程启动后就应该开始执行 firstTask 的 run 方法了。然后一切并没有这么简单,我们先从 Worker 的定义开始

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) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

通过 Worker 的构造方法可知,addWorker() 中创建的线程内部的 target 并不是 firstTask,而是包装它的那个 worker:this.thread = getThreadFactory().newThread(this);。而 Worker 刚好实现了 Runnable 接口。既然这样,线程启用后执行的就是 worker 中的 run 方法,如下

/** Delegates main run loop to outer runWorker  */
public void run() {
    runWorker(this);
}

所以找了一圈才到执行任务的方法,进入 runWorker()

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        
        // 有任务执行
        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 {
                // 任务执行前调用,需自定义实现
                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);
    }
}

如果线程池启动后第一次执行任务,这时候 firstTask 不为空(这里只是举个例子),则直接执行这个任务。任务为空,则调用 getTask() 获取任务。

getTask()

源码如下

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);
        
        // Are workers subject to culling?
        // allowCoreThreadTimeOut:是否允许核心线程超时,默认为 false,
        // wc > corePoolSize:当线程数大于核心线程数,对于多余的线程需要进行超时控制
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // timed 为 true,说明此时线程数大于核心线程数,
            // 调用 workQueue.poll() 获取任务,等待 keepAliveTime 时间后还没有任务,线程需要被回收掉
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

从任务队列中拿任务分为两种情况:

  • 线程数量 > 核心线程数,在从任务队列中拿(poll)任务超时(keepAliveTime)后,方法返回 null;
  • 线程数量 <= 核心线程数,从队列拿任务,如果队列为空,线程阻塞(take()),直到拿到任务为止。

第一种情况,线程等待任务超时后,将结束 runWorker() 方法中的 while 循环,执行 finally 中的processWorkerExit(w, completedAbruptly);方法:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // ...
    workers.remove(w);
    // ...
}

从 workers 中移除该线程,方法执行结束后,worker 中线程将被 JVM 回收。

最佳实践

下面总结了工作了关于线程池的一些最佳实践

线程池大小

并发经典《Java 并发编程实战》中提到,线程池的大小应该根据应用场景区分:CPU 密集型和 IO 密集型。

  • CPU 密集型:线程数 = CPU 的核数;
  • IO 密集型:线程数 = 2 * CPU 核数 + 1;
posted @ 2022-06-08 18:40  Tailife  阅读(30)  评论(0编辑  收藏  举报