JUC-并发编程17-线程池深入分析-ThreadPoolExecutor-1

1、简介

ThreadPoolExecutor的构造方法是创建线程池的入口,虽然比较简单,但是信息量很大,由此也能引发一系列的问题,同样地,这也是面试中经常被问到的问题,下面只是列举了一部分关于ThreadPoolExecutor构造方法的问题。

2、属性说明

2.1 corePoolSize

除非设置了allowCoreThreadTimeOut ,否则核心池大小是保持活动状态(并且不允许超时等)的最小工作程序数,在这种情况下,最小值为零。

当正在运行的线程数小于核心线程数时,来一个任务就创建一个核心线程;当正在运行的线程数大于或等于核心线程数时,任务来了先不创建线程而是丢到任务队列中。

2.2 maximumPoolSize

最大线程数。请注意,实际最大值在内部*受CAPACITY限制。当任务队列满了时,来一个任务才创建一个非核心线程,但不能超过最大线程数。

2.3 keepAliveTime  + unit

线程保持空闲时间及单位。默认情况下,此两参数仅当正在运行的线程数大于核心线程数时才有效,即只针对非核心线程。但是,如果allowCoreThreadTimeOut被设置成了true,针对核心线程也有效。即当任务队列为空时,线程保持多久才会销毁,内部主要是通过阻塞队列带超时的poll(timeout, unit)方法实现的。

2.4 workQueue

任务队列,当正在运行的线程数大于或等于核心线程数时,任务来了是先进入任务队列中的。这个队列必须是阻塞队列,所以像ConcurrentLinkedQueue就不能作为参数,因为它虽然是并发安全的队列,但是它不是阻塞队列。

用于保留任务并移交给工作线程的队列。我们不需要返回null的workQueue.poll()必然意味着workQueue.isEmpty(),因此仅依靠isEmpty来查看队列是否为空(例如,在确定是否从过渡时,我们必须关闭并整理)。这可容纳特殊用途的队列,例如DelayQueues,允许poll()返回null,即使延迟到期后它可能稍后返回非null。

2.5 threadFactory

线程工厂。

默认使用的是Executors工具类中的DefaultThreadFactory类,这个类有个缺点,创建的线程的名称是自动生成的,无法自定义以区分不同的线程池,且它们都是非守护线程。自定义一个线程工厂,其实也很简单,自己实现一个ThreadFactory,然后把名称和是否是守护进程当作构造方法的参数传进来就可以了。

可以参照

  1. io.netty.util.concurrent.DefaultThreadFactory

  2. com.google.common.util.concurrent.ThreadFactoryBuilder

2.6 allowCoreThreadTimeOut

如果为false(默认),则即使处于空闲状态,核心线程也保持活动状态。 如果为true,则核心线程使用keepAliveTime来做超时等待工作。

2.7 handler-rejectedExecutionHandler

任务拒绝策略处理器 
线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常

2.7.1 两种情况会拒绝处理任务:

  1. 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
  2. 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务

2.7.2 ThreadPoolExecutor类有几个内部实现类来处理这类情况:

  1. AbortPolicy 丢弃任务,抛运行时异常
  2. CallerRunsPolicy 执行任务
  3. DiscardPolicy 忽视,什么都不会发生
  4. DiscardOldestPolicy  从队列中踢出最先进入队列(最后一个执行)的任务

2.7.3 实现RejectedExecutionHandler接口,可自定义处理器

3、构造函数

3.1 默认的线程工厂和拒绝的执行处理程序

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

3.2 自定义线程工厂和默认拒绝策略

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

3.3 默认线程工厂和自定义拒绝策略

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

3.4 参数全自定义创建

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue 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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

4、线程的生命周期

关于线程的生命周期,我们可以看一下java.lang.Thread.State这个类,这个类,它是线程的内部枚举类,定义了线程的各种状态,并且注释也很清晰。

源码如下:

public enum State {
        /**
         * 尚未启动的线程的线程状态。
         */
        NEW,

        /**
         *可运行状态,正在运行或者在等待系统资源,比如CPU
         */
        RUNNABLE,

        /**
         * 阻塞状态,在等待一个监视器锁(也就是我们常说的synchronized)
         * 或者在调用了Object.wait()方法且被notify()之后也会进入BLOCKED状态
         */
        BLOCKED,

        /**
         * 等待线程的线程状态。 调用以下方法之一,线程处于等待状态:
         * 1. Object.wait()无超时的方法后且未被notify()前,如果被notify()了会进入BLOCKED状态
         * 2. Thread.join()无超时的方法后
         * 3. LockSupport.park()无超时的方法后
         */
        WAITING,

        /**
         * 超时等待状态,在调用了以下方法后会进入超时等待状态:
         * 1. Thread.sleep()方法后
         * 2. Object.wait(timeout)方法后且未到超时时间前,如果达到超时了或被notify()了会进入            
         * BLOCKED状态
         * 3.Thread.join(timeout)方法后
         * 4.LockSupport.parkNanos(nanos)方法后
         * 5. LockSupport.parkUntil(deadline)方法后
         */
        TIMED_WAITING,

        /**
         * 线程终止的线程状态。 线程执行完毕。
         */
        TERMINATED;
    }

生命周期图如下:

JUC-并发编程17-线程池深入分析-ThreadPoolExecutor-1_第1张图片

  1. 为了方便讲解,我们把锁分成两大类,一类是synchronized锁,一类是基于AQS的锁(我们拿重入锁举例),也就是内部使用了LockSupport.park()/parkNanos()/parkUntil()几个方法的锁;
  2. 不管是synchronized锁还是基于AQS的锁,内部都是分成两个队列,一个是同步队列(AQS的队列),一个是等待队列(Condition的队列);
  3. 对于内部调用了object.wait()/wait(timeout)或者condition.await()/await(timeout)方法,线程都是先进入等待队列,被notify()/signal()或者超时后,才会进入同步队列;
  4. 明确声明,BLOCKED状态只有线程处于synchronized的同步队列的时候才会有这个状态,其它任何情况都跟这个状态无关;
  5. 对于synchronized,线程执行synchronized的时候,如果立即获得了锁(没有进入同步队列),线程处于RUNNABLE状态;
  6. 对于synchronized,线程执行synchronized的时候,如果无法获得锁(直接进入同步队列),线程处于BLOCKED状态;
  7. 对于synchronized内部,调用了object.wait()之后线程处于WAITING状态(进入等待队列);
  8. 对于synchronized内部,调用了object.wait(timeout)之后线程处于TIMED_WAITING状态(进入等待队列);
  9. 对于synchronized内部,调用了object.wait()之后且被notify()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;
  10. 对于synchronized内部,调用了object.wait(timeout)之后且被notify()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;
  11. 对于synchronized内部,调用了object.wait(timeout)之后且超时了,这时如果线程正好立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;
  12. 对于synchronized内部,调用了object.wait()之后且被notify()了,如果线程无法获得锁(也就是进入了同步队列),线程处于BLOCKED状态;
  13. 对于synchronized内部,调用了object.wait(timeout)之后且被notify()了或者超时了,如果线程无法获得锁(也就是进入了同步队列),线程处于BLOCKED状态;
  14. 对于重入锁,线程执行lock.lock()的时候,如果立即获得了锁(没有进入同步队列),线程处于RUNNABLE状态;
  15. 对于重入锁,线程执行lock.lock()的时候,如果无法获得锁(直接进入同步队列),线程处于WAITING状态;
  16. 对于重入锁内部,调用了condition.await()之后线程处于WAITING状态(进入等待队列);
  17. 对于重入锁内部,调用了condition.await(timeout)之后线程处于TIMED_WAITING状态(进入等待队列);
  18. 对于重入锁内部,调用了condition.await()之后且被signal()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;
  19. 对于重入锁内部,调用了condition.await(timeout)之后且被signal()了,如果线程立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;
  20. 对于重入锁内部,调用了condition.await(timeout)之后且超时了,这时如果线程正好立即获得了锁(也就是没有进入同步队列),线程处于RUNNABLE状态;
  21. 对于重入锁内部,调用了condition.await()之后且被signal()了,如果线程无法获得锁(也就是进入了同步队列),线程处于WAITING状态;
  22. 对于重入锁内部,调用了condition.await(timeout)之后且被signal()了或者超时了,如果线程无法获得锁(也就是进入了同步队列),线程处于WAITING状态;
  23. 对于重入锁,如果内部调用了condition.await()之后且被signal()之后依然无法获取锁的,其实经历了两次WAITING状态的切换,一次是在等待队列,一次是在同步队列;
  24. 对于重入锁,如果内部调用了condition.await(timeout)之后且被signal()或超时了的,状态会有一个从TIMED_WAITING切换到WAITING的过程,也就是从等待队列进入到同步队列;

5、线程池的生命周期

5.1 图文介绍

我们先来看一下线程池ThreadPoolExecutor中定义的生命周期中的状态及相关方法:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3; //32-3=29
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // 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;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // 线程池的状态
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    //线程池中工作线程的数量
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    // 计算ctl的值,等于运行状态“加上”线程数量
    private static int ctlOf(int rs, int wc) { return rs | wc; }
  1. 线程池的状态和工作线程的数量共同保存在控制变量ctl中,类似于AQS中的state变量,不过这里是直接使用的AtomicInteger,这里换成unsafe+volatile也是可以的;
  2. ctl的高三位保存运行状态,低29位保存工作线程的数量,也就是说线程的数量最多只能有(2^29-1)个,也就是上面的CAPACITY;
  3. 线程池的状态一共有五种,分别是RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED;
  4. RUNNING,表示可接受新任务,且可执行队列中的任务;
  5. SHUTDOWN,表示不接受新任务,但可执行队列中的任务;
  6. STOP,表示不接受新任务,且不再执行队列中的任务,且中断正在执行的任务;
  7. TIDYING,所有任务已经中止,且工作线程数量为0,最后变迁到这个状态的线程将要执行terminated()钩子方法,只会有一个线程执行这个方法;
  8. TERMINATED,中止状态,已经执行完terminated()钩子方法;

下面我们再来看看这些状态之间是怎么流转的:

JUC-并发编程17-线程池深入分析-ThreadPoolExecutor-1_第2张图片

  1. 新建线程池时,它的初始状态为RUNNING,这个在上面定义ctl的时候可以看到;
  2. RUNNING->SHUTDOWN,执行shutdown()方法时;
  3. SHUTDOWN->STOP,执行shutdownNow()方法时;
  4. STOP->TIDYING,执行了shutdown()或者shutdownNow()后,所有任务已中止,且工作线程数量为0时,此时会执行terminated()方法;
  5. TIDYING->TERMINATED,执行完terminated()方法后;

5.2 源码分析

5.2.1 RUNNING

RUNNING,比较简单,创建线程池的时候就会初始化ctl,而ctl初始化为RUNNING状态,所以线程池的初始状态就为RUNNING状态。

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

5.2.2 SHUTDOWN

执行shutdown()方法时把状态修改为SHUTDOWN,这里肯定会成功,因为advanceRunState()方法中是个自旋,不成功不会退出。

public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
private void advanceRunState(int targetState) {
        for (;;) {
            int c = ctl.get();
            if (runStateAtLeast(c, targetState) ||
                ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
                break;
        }
    }

5.2.3 STOP

执行shutdownNow()方法时,会把线程池状态修改为STOP状态,同时标记所有线程为中断状态。

 public List shutdownNow() {
        List tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            // 修改为STOP状态
            advanceRunState(STOP);
            // 标记所有线程为中断状态
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

至于线程是否响应中断其实是在队列的take()或poll()方法中响应的,最后会到AQS中,它们检测到线程中断了会抛出一个InterruptedException异常,然后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.
            //如果状态为STOP了,这里会直接退出循环,且减少工作线程数量
            //退出循环了也就相当于这个线程的生命周期结束了
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
            int wc = workerCountOf(c);
            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
            try {
                //真正响应中断是在poll()方法或者take()方法中
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                //这里捕获中断异常
                timedOut = false;
            }
        }
    }

源码上的注释:

根据当前配置设置执行阻塞或定时等待任务,或者如果此工作程序因以下任何原因而必须退出,则返回null:*

  1. 超过了maximumPoolSize工作程序(由于调用setMaximumPoolSize)。
  2. 池已停止。
  3. 池已关闭,队列为空。
  4. 该工作程序等待任务超时,并且超时,工作程序将终止(即 {@code allowCoreThreadTimeOut || workerCount> corePoolSize})在定时等待之前和之后,以及队列是非空的,此工作程序不是池中的最后一个线程。

这里有一个问题,就是已经通过getTask()取出来且返回的任务怎么办?实际上它们会正常执行完毕,有兴趣的同学可以自己看看runWorker()这个方法,我们下一节会分析这个方法。

5.2.4 TIDYING

当执行shutdown()或shutdownNow()之后,如果所有任务已中止,且工作线程数量为0,就会进入这个状态。

final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            // 三种情况不会执行后续代码 1.RUNNING 2.状态的值比TIDYING还大,也就是TERMINATED 
            // 3.SHUTDOWN状态且任务队列不为空
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
            //工作线程数量不为0,也不会执行后续代码
            if (workerCountOf(c) != 0) { // Eligible to terminate
                 尝试中断空闲的线程
                interruptIdleWorkers(ONLY_ONE);
                return;
            }
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                //
                // CAS修改状态为TIDYING状态
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                        // 更新成功,执行terminated钩子方法
                        terminated();
                    } finally {
                        
                        // 强制更新状态为TERMINATED,这里不需要CAS了
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

实际更新状态为TIDYING和TERMINATED状态的代码都在tryTerminate()方法中,实际上tryTerminated()方法在很多地方都有调用,比如shutdown()、shutdownNow()、线程退出时,所以说几乎每个线程最后消亡的时候都会调用tryTerminate()方法,但最后只会有一个线程真正执行到修改状态为TIDYING的地方。

修改状态为TIDYING后执行terminated()方法,最后修改状态为TERMINATED,标志着线程池真正消亡了。

5.2.5 TERMINATED

见 5.2.4 

下一期我们将开始学习线程池执行任务的主流程。

你可能感兴趣的:(并发编程,数据结构与算法,java基础,java,并发编程)