Java提高篇——深入理解Java线程池和ThreadPoolExecutor源码解析

事先声明:本文转载来源深入理解Java线程池:ThreadPoolExecutor感谢这位同学
虽然是转载,但是内容还是有一些不同的,希望大家细细品味。

Java线程池

线程池介绍

线程池的概念

在web开发当中,服务器需要接受并处理请求,所以会为一个请求分配一个线程来进行处理。如果每次请求都新创建一个线程的话实现起来非常简单,但是存在一个问题:
如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁线程,如此一来会大大降低系统的效率。可能出现服务器在为每个请求创建新的线程和销毁线程上花费时间和资源很多但是处理实际用户请求的时间和资源较少的情况。

那么就需要一种办法执行完一个任务,并不被销毁,而是可以继续执行其他的任务。

这就是线程池的目的。线程池为线程生命周期的开销和资源不足问题提供了解决方案。线程池就是首先创建一些线程,他们的集合称为线程池。使用线程池可以很好的提高性能,线程池在系统启动时创建大量的空闲线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,线程不会死亡,而是再次返回到线程池当中成为空闲状态,等待执行下一个任务。通过对多个任务重用线程,线程创建的开销被分摊到多个任务上。

使用线程池的情况

1、单个任务处理时间比较短
2、需要处理的任务量很大

使用线程池的优点

1、降低资源消耗。通过重复利用已经创建的线程降低线程创建和销毁造成的消耗。
2、提高响应速度。当任务到达的时候,不需要创建新的线程,所以任务不需要等待创建新的线程去执行。
3、提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以对线程进行统一的分配,调优和监控。

线程池的类图
在这里插入图片描述

Executor框架接口

public interface Executor {
    void execute(Runnable command);
}

通常我们开启一个线程并且执行都是这样的

Thread thread = new Thread();
thread.start();

但是使用线程池开启一个线程就是下面这样:

ExecutorService executorService = Executors.newSingleThreadExecutor();
Thread thread = new Thread();
executorService.execute(thread);

这只是简单的一个示例,对于不同的Executor实现,execute()方法有三种状态:

  • 可能是创建一个新的线程并启动。
  • 也有可能是使用已有的工作线程来运行传入的任务。
  • 也可能是根据设置线程池的容量或阻塞队列的容量来决定是否要将传入的线程放入阻塞队列中或者是拒绝接受传入的线程。
ExecutorService接口
package java.util.concurrent;
import java.util.List;
import java.util.Collection;

public interface ExecutorService extends Executor {

    void shutdown();

    List<Runnable> shutdownNow();

    boolean isShutdown();

    boolean isTerminated();

    boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

    <T> Future<T> submit(Callable<T> task);

    <T> Future<T> submit(Runnable task, T result);

    Future<?> submit(Runnable task);

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

ExecutorService接口

  • ExecutorService接口继承自Executor接口,提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成的Future的方法
  • 增加了shutDown(), shutDownNow(), invokeAll(), invokeAny() 和submit() 等方法。
  • 如果需要支持及时关闭,也就是shutDownNow() 方法,则任务需要正确处理中断。
ScheduledExecutorService接口
package java.util.concurrent;

public interface ScheduledExecutorService extends ExecutorService {

    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);

    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
    
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
}

ScheduledExecutorService扩展ExecutorService接口并增加了schedule方法。

  • 调用schedule方法可以在指定的演示后执行一个Runnable或者Callable任务。
  • ScheduledExecutorService接口还定义了按照指定时间间隔定期执行任务的scheduleAtFixedRate()方法和scheduleWithFixedDelay()方法。

ThreadPoolExecutor分析

ThreadPoolExecutor继承自AbstractExecutorService,也是实现了ExecutorService接口。

这里面的东西很多,挑几个重要的字段进行一下解释。

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
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;

// 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; }
  • ctl是对线程池的运行状态线程池中有效线程的数量进行控制的一个字段,包含两部分的信息:
    - 线程池的运行状态(runState)
    - 线程池内有效线程的数量(workerCount)
  • ctl使用了Integer类型来保存,高3位存储runState,低29位保存workCount。
    • 我们来计算一下是为什么,CAPACITY是1左移29位-1,所以低29位都是CAPACITY。之后取workerCount的时候(使用的是workerCountOf(int c)方法),29个1 和一个int值取&,那就是低29位的值,所以低29位保存workCount。
    • 取出runState的时候,CAPACITY取反,也就是低29位变为0,高三位变为1,所以和int值取&的时候是高三位的值。

后面是线程池状态,线程池一共有五种状态

线程池五种状态

1、RUNNING:能接收新提交的任务,并且也能处理阻塞队列中的任务。-----》 能提交,能处理
2、SHUTDOWN:关闭状态,不再接收新提交的任务,但却可以继续处理阻塞队列中已保护的任务,在线程池处于RUNNING状态时,调用shutdown() 方法会使线程池进入到该状态。(finalize()方法在执行过程中也会调用shutdown()方法进入该状态)-----》不能提交,能处理
3、STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于RUNNING或者SHUTDOWN状态时,调用shutdownNow() 方法会使线程池进入到该线程。-----》不能提交,也不能处理任务
4、TIDYING:如果所有的任务都已经终止了,workerCount(有效线程数) 为0,线程池进入该状态之后会调用terminated() 方法进入TERMINATED状态。
5、TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated() 方法中什么也没有做。

进入TERMINATED的条件如下:
(1)线程池不是RUNNING状态
(2)线程池不是TIDYING状态或者是TERMINATED状态
(3)如果线程池状态是SHUTDOWN并且workQueue为空
(4)workerCount为0
(5)设置TIDYING状态成功

线程池的状态转换过程

在这里插入图片描述
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; }

runStateOf:获取运行状态
workerCountOf:获取活动线程数
ctlOf:获取运行状态和活动线程数的值

ThreadPoolExecutor构造方法
public class ThreadPoolExecutor extends AbstractExecutorService {
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

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

    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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
}

构造方法当中字段的含义:
(1)corePoolSize:核心线程数量,当有新任务在execute()方法提交时,会执行如下判断:

  • 如果运行的线程少于corePoolSize,就创建新线程来处理任务,即使线程池中的其他线程是空闲的。(运行线程数 < corePoolSize
  • 如果线程池当中的线程数量大于等于corePoolSize且小于maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务。(线程数 > corePoolSize && 线程数 < maximumPoolSize && workQueue is full)
  • 如果设置的corePoolSize和maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新的任务提交,如果workQueue未满,则将请求放入到workQueue中,等待有空闲的线程去从workQueue中领取任务并处理。(corePoolSize == maximumPoolSize && workQueue is not full ==》 空闲线程领任务
  • 如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,通过handler所指定的策略来处理任务。(线程数量 >= maximumPoolSize && workQueue is full ==》 wait handler process

所以任务提交的时候进行检验的顺序是corePoolSize -> workQueue -> maximumPoolSize
(2)maximumPoolSize:最大线程数量
(3)workQueue:等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列。
(4)workQueue:保存等待执行的任务的阻塞队列,当提交一个新的任务到线程池以后,线程池会根据当前线程池中正在运行着的线程的数量来决定对该任务的处理方式,主要有以下几种处理方式:
- 直接切换:这种方式常用的队列是SynchronousQueue,还等待研究,等有时间写一篇博客来介绍以下
- 使用无界队列:使用基于链表的阻塞队列LinkedBlockingQueue。使用这种方式,线程当中能创建的最大线程数量是corePoolSize,而maximumPoolSize不会起作用。当线程池中所有的核心线程都是RUNNING状态的时候,这时一个新的任务提交就会放入到等待队列当中。
- 使用有界队列:一般使用ArrayBlockingQueue。使用该方式可以将线程池的最大线程数量限制为maximumPoolSize,这样能降低资源的消耗,但同时这种方式也使得线程池对线程的调度变得困难,因为线程池和队列的容量都是有限的值,所以想要使线程池处理任务的吞吐率达到一个相对合理的范围,又想使线程调度变得简单,还要尽可能的降低线程池对资源的消耗,就需要合理的设置队列和容量的大小。
- 如果想要降低系统资源的消耗(CPU的使用率,操作系统资源的消耗,上下文环境切换的开销等),可以设置较大的队列容量较小的线程池容量,但这样也会降低线程处理任务的吞吐量。
- 如果提交的任务经常发生阻塞,那么可以考虑通过调用setMaximumPool()方法来重新设定线程池的容量
- 如果队列的容量设置的比较小,通常需要将线程池的容量设置的大一些,这样CPU的使用率会相对的高一些。但是如果线程池的容量设置的太大, 那么在提交任务数量太多的情况下,并发量会增加,那么线程之间的调度就是一个需要考虑的问题,因为这样反而有可能降低处理任务的吞吐量。
(5)keepAliveTime:线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果此时没有新的任务提交,核心线程外的线程不会立即销毁,会进入等待状态,直到等待时间超过了keepAliveTime。
(6)threadFactory:他是ThreadFactory类型的变量,用来创建新的线程。默认使用的Executors.defaultThreadFactory();来创建线程,使用默认的ThreadFactory来创建线程时,会使新的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。
(7)handler:handler是RejectedExecutionHandler类型的变量,表示线程池的饱和策略。如果队列塞满了并且没有空闲线程,如果还提交任务,就需要采取一些策略处理这个任务。线程池提供了四种策略:
- AbortPolicy:直接抛出异常,默认策略
- CallerRunsPolicy:用调用者所在的线程执行任务。
- DiscardOldsetPolicy:丢弃阻塞队列最靠前的任务,执行当前任务。
- DiscardPolicy:直接丢弃任务。

execute() 方法
/**
 * 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}.
 * 
 * 如果任务不能被提交执行,或者因为这个执行器已经被shutdown了或者是因为容量已经到达上限,
 * 该任务将被当前的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) {
	/*
     * 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.
     * 
     * 如果比corePoolSize小的线程正在执行,尝试开启新的线程,并以被给予的command作为第一次的任务。
     * 调用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.
     * 
     * 如果一个任务被成功提交到队列当中,那么仍然会进行二次检查,检查我们是否已经增加了一个线程
     * (因为从check到现在的过程当中这个线程可能死亡)或者线程池shut down自从进入这个方法之后,
     * 所以需要再次检查状态,或者在线程停止的时候是否成功的回滚队列,
     * 或者是在没有线程的情况下新建一个线程。
     *
     * 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.
     * 
     * 如果任务不能排队,那么尝试新建一个线程,如果失败,我们知道我们正在处于shutdown状态,
     * 或者已经饱和,并且拒绝这个任务。
     */
    if (command == null)
        throw new NullPointerException();
    
    //ctl记录runState和workerCount的数据
    int c = ctl.get();
    
    //workerCountOf是从低29位取数,如果小于核心线程数量,添加任务(addWorker)
    if (workerCountOf(c) < corePoolSize) {
   		//addWorker的第二个参数是限制添加线程的数量是corePoolSize还是maximumPoolSize
   		//true  --->  corePoolSize
   		//false --->  maximumPoolSize
        if (addWorker(command, true))
            return;
            
        //如果添加失败,重新获取ctl
        c = ctl.get();
    }

	//如果线程池是运行状态,并且任务添加到队列成功
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        //重新获取ctl

		//二次判断线程池的运行状态,如果不是运行状态,移除刚才不能执行的指令commend
		//执行之后handler使用拒绝策略对任务进行处理,整个方法返回

		/**
		 * 注意是! isRunning(recheck) && remove(command) 也就是!(isRunning(recheck)) && remove(command)
		 * 意思就只要线程不在执行状态那么就移除command,并且拒绝任务的进入。不得不说,想的相当周到。
		 */
        if (! isRunning(recheck) && remove(command))
            reject(command);

		//查看线程池当中的有效线程数,如果是0,那么addWorker
		//参数详情如下:
		//1、第一个是null,表示线程池当中创建一个线程,但不执行
		//2、第二个参数是false,就是把线程池的有限线程数量上升到maximumPoolSize,添加线程的时候根据maximumPoolSize进行判断
		//如果判断的workerCount数量大于0,直接返回,之前添加到workQueue当中的commend会在将来的某个时候被执行
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }

	//执行下面这条语句的话,可能存在两种情况
	//1、线程池不在RUNNING状态下
	//2、线程在RUNNING状态下,但是workerCount >= corePoolSize 并且workQueue已满
	//这时再次尝试addWorker,线程池上限设置为maximumPoolSize,失败的话就拒绝这个任务
    else if (!addWorker(command, false))
        reject(command);
}

总结一下,在执行execute()方法的时候,如果线程池处于运行状态的话,执行流程如下:
1、如果workerCount < corePoolSize,就创建并且启动一个线程来执行刚刚提交的任务
2、如果workerCount >= corePoolSize,并且线程池内的阻塞队列未满,添加新的任务到阻塞队列当中。
3、如果workCount >= corePoolSize && workCount < maximumPoolSize,并且线程池的阻塞队列满,创建并启动新的线程来执行新提交的任务。
4、如果workCount >= maximumPoolSize,并且线程池阻塞队列满,根据拒绝策略来处理任务,默认的处理方式是抛出异常。

需要注意的是addWorker(null, false)方法,这个方法虽然没有传入任务,但是任务在之前已经放到workQueue当中了,所以worker在执行的时候,会直接从workQueue当中获取任务。所以,在workerCountOf(recheck) == 0的时候,也就是没有空闲线程,就按照maximumPoolSize为上限增加新的线程,这是为了保证线程池在RUNNING状态下必须要有一个线程来执行任务。

执行方法execute()的流程图在这里插入图片描述

addWorker方法

addWorker方法的主要工作是在线程池当中创建一个新的线程并且执行

  • firstTask参数用于指定新增的线程执行的第一个任务
  • core参数
    • true表示在新增线程时会判断当前活动线程数是否少于corePoolSize
    • false表示新增线程前需要判断当前活动的线程数是否少于maximumPoolSize。

代码演示

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        //获取运行状态
        int rs = runStateOf(c);

        // rs >= SHUTDOWN,表示不接受新的任务
        // 之后判断三个条件,凡是有一个不满足,直接false
        // 1、rs == SHUTDOWN 就是关闭状态,不接受新的提交任务,但是可以处理阻塞队列当中的任务
        // 2、firstTask为空
        // 3、阻塞队列不为空
        
        // 首先考虑rs == SHUTDOWN 
        // 这种状态不接收新的提交的任务,所以在firstTask不为空的时候,说明有新的任务过来,直接false
        // 如果是null,并且workQueue也是空的,那么返回false,说明没有任务可做,不需要再添加线程
        
        /**
         * 第一个条件是运行状态是SUTDOWN以上的 也就都是不允许提交的情况
         * 第二个条件如果有一个不满足,直接返回false
         * 1、运行状态是SHUTDOWN
         * 2、第一个任务为空
         * 3、阻塞队列非空
         * 
         * 当rs == SHUTDOWN的时候
         * 1、不允许提交任务,所以firstTask不为空的时候返回false
         * 2、如果firstTask为空,并且workQueue为空,返回false
         * 因为这样阻塞队列当中已经没有任务了,不需要再添加线程。
         * 3、如果firstTask为空,而且等待队列非空,那么需要添加新的线程去执行任务
         */
        if (rs >= SHUTDOWN && 
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
        	//获取当前线程数
            int wc = workerCountOf(c);
           	
            /**
             * 如果wc超过capacity,也就是ctl的低29位的最大值(二进制是29个1)返回false,达到最大线程数量就不允许提交任务了,直接return false;
             * 
             * 这里的core的值是addWorker方法的第二个参数
             * 如果为true表示根据corePoolSize来比较  
             * 如果为false,就根据maximumPoolSize来比较
             */ 
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 比较并且增加workerCount,如果成功,跳出for循环
            if (compareAndIncrementWorkerCount(c))
                break retry;  
            // 如果增加workerCount失败,重新获取ctl的值
            c = ctl.get();  // Re-read ctl
            // 如果当前运行状态不是rs,说明状态发生了改变,进入for循环继续执行
            if (runStateOf(c) != rs)
                continue retry;
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    	// 根据firstTask来创建Worker对象
        w = new Worker(firstTask);
        // 一个Worker创建一个线程
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());
                
                // rs < SHUTDOWN 表示线程池现在是RUNNING
                // 如果rs是运行状态,或者是SHUTDOWN并且firstTask是空的时候,往线程池当中添加线程
                // 因为在SHUTDOWN时不会添加新的任务,但是还是会执行workQueue中还没执行的任务
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // 检查t线程是不是可以启动的
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    // 如果workers的数量比当前线程池出现的最大线程数量还大,修改最大线程数量
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
            	// 到最后解锁
                mainLock.unlock();
            }
            
            //如果任务成功的加到线程池当中,启动线程
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
    	/**
    	 * 如果最后新建的线程还没开启,那么执行addWorkerFailed,线程开启失败
    	 */
        if (! workerStarted)
            addWorkerFailed(w);
    }
    // 最后返回线程开启状态
    return workerStarted;
}

后面有一个t.start() 这个语句,启动的时候会调用Worker类中的run方法,Worker本身实现了Runnable接口,所以一个Worker类型的对象也是一个线程。

Worker类

线程池中的每一个线程被封装成Worker对象,ThreadPool维护的其实就是一组Worker对象。

Worker类的源码
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {

    private static final long serialVersionUID = 6138294804551838833L;

	//存放构造方法当中生成的线程
    final Thread thread;
    
    //保存传入的任务
    Runnable firstTask;
    
    volatile long completedTasks;

    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    public void run() {
        runWorker(this);
    }
    
    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;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

Worker类继承了AQS,并实现了Runnable接口,注意其中的firstTask和thread属性:

  • firstTask用它来保存传入的任务;
  • thread是在调用构造方法时通过ThreadFactory来创建的线程,是用来处理任务的线程。

在调用构造方法的时候,需要把任务传入,通过getThreadFactory().newThread(this)来新建一个线程,newThread方法传入的参数是this,因为Worker本身继承了Runnable接口,也就是一个线程,所以一个 Worker对象在启动的时候会调用Worker类中的run方法。

Worker类继承AQS,使用AQS来实现独占锁的功能。为什么不使用ReentrantLock来实现呢??有这么样一个方法tryAcquire,他是不允许重入的,但是ReentrantLock是允许重入的:

1、lock方法一旦获取了独占锁,表示当前线程正在执行任务中
2、如果正在执行任务,则不应该中断线程
3、如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断
4、线程池在执行shutdown方法或者是tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否在空闲状态。
5、之所以设置为不可重入,是因为我们不希望任务在调用像setCorePoolSize这样的线程池控制方法时重新获取锁。如果使用ReentrantLock是可重入的,这样如果在任务当中调用了setCorePoolSize这类线程池控制的方法,会中断正在运行的线程。

所以Worker继承自AQS,用于判断线程是否空闲以及是否可以被中断。

此外,在构造方法中执行了setState(-1),把state变量设置为-1,那么这么做的原因是什么呢??是因为AQS中默认的state是0,如果刚创建了一个Worker对象,还没有执行任务时,这时就不应该中断,看一下tryAcquire() 方法:

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

tryAcquire() 方法是根据state是否是0来判断的,所以,setState(-1)将state设置为-1是为了禁止在执行任务前对线程进行中断。

正是因为这样,在runWorker方法当中会先调用Worker对象的unlock方法将state设置为0

runWorker方法
final void runWorker(Worker w) {
    Thread wt= Thread.currentThread();
    //获取第一个任务
    Runnable task = w.firstTask;
    w.firstTask = null;
    //允许线程发生中断
    w.unlock();
    //是否因为异常退出循环,后面有判断
    boolean completedAbruptly = true;
    try {
    	//如果task为null,则通过getTask获取任务
        while (task != null || (task = getTask()) != null) {
            w.lock();
            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);
    }
}

第一个if判断,目的在于:

  • 如果线程池正在停止,那么要保证当前线程是中断状态
  • 如果不是的话,要保证线程不是中断状态

这里要考虑在执行该if语句期间可能也执行了shutdownNow方法,shutdownNow方法会把状态设置为STOP(STOP状态,不能接受新的任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池中处于RUNNING和SHUTDOWN状态时,调用shutdownNow() 方法会使线程池进入到这个状态)

STOP状态要中断线程池当中的所有线程,而这里使用Thread.interrupted() 来判断是否中断是为了确保在RUNNING或者是SHUTDOWN状态时线程是非中断状态的,因为Thread.interrupted() 方法会复位中断的状态。

runWorker方法的执行流程
1、while循环不断的通过getTask() 方法获取任务
2、getTask() 方法从阻塞队列中取出任务
3、如果线程池正在停止,那么要保证当前线程时中断状态,否则要保证当前线程不是中断状态
4、调用task.run() 执行任务
5、如果task为null则跳出循环,执行processWorkerExit() 方法
6、runWorker方法执行完毕,也代表着Worker中的run方法执行完毕,销毁线程。

注意
在这里beforeExecute方法和afterExecute方法在ThreadPoolExecutor当中是空的,这留给子类去实现。
completedAbruptly变量来表示在执行任务过程当中是否出现了异常,在processWorkerExit方法中会对该变量的值进行判断。

getTask() 方法(从阻塞队列当中获取任务)
private Runnable getTask() {
	//timeOut变量的值表示上次从阻塞队列中取出任务时是否超时
    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.

		//如果线程池状态 rs >= SHUTDOWN,也就是非RUNNING状态,再进行如下判断:
		//1、rs >= STOP,判断线程池是否正在stop
		//2、阻塞队列是否为空
		//如果上述条件都满足,则将workerCount-1,并且返回null
		//因为如果当前线程池状态的值是SHUTDOWN或以上时,不允许再向阻塞队列中添加任务。
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        //timed变量用于判断是否需要进行超时控制
        //allowCoreThreadTimeOut默认是false,也就是核心线程不允许超时
        //wc > corePoolSize,表示当前线程池当中的线程数量大于核心线程数量
        //对于超过核心线程数量的这些线程,需要进行超时控制
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
		
		//wc > maximumPoolSize的情况是因为可能在此方法执行阶段同时执行setMaximumPoolSize方法
		//timed && timedOut 如果为true,表示当前操作需要进行超时控制,并且上次从阻塞队列中湖区任务发生了超时
		//接下来继续判断,如果有效线程的数量大于1,或者阻塞队列是空的,那么尝试将workerCount-1
		//如果-1成功,返回null
		//如果-1失败,那么continue,从新尝试
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
        	//timed进行判断,如果为true,通过队列的poll方法进行超时控制,如果在keepAliveTime时间内没有没有获得到任务,返回null
        	//否则通过take方法,如果这时队列为空,则take方法会阻塞直到队列不为空
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
       		//如果获取任务时当前线程发生了中断,则设置timedOut为false并且循环重新尝试
            timedOut = false;
        }
    }
}

重要的是第二if判断,目的是控制线程池的有效线程数量。由上文中的分析可以知道,在执行execute方法时,如果当前线程池的线程数量超过了corePoolSize且小于maximumPoolSize,并且workQueue已满的时候,就可以增加工作线程,但这时如果超时没有获取到任务,也就是timedOut为true的情况,说明workQueue已经为空了,也就说明了当前线程池中不需要那么多线程来执行任务了,这时就可以把多于corePoolSize数量的变成销毁掉,保持线程数量在corePoolSize即可。

那什么时候会销毁呢??是在runWorker方法执行完毕之后,也就是Worker中的run方法执行完,JVM自动回收。

getTask方法返回null时,在runWorker方法中会跳出while循环,然后会执行processWorkerExit方法。

processWorkerExit() 方法
private void processWorkerExit(Worker w, boolean completedAbruptly) {
	//如果completedAbruptly值为true,则说明线程执行时出现了异常,需要将workerCount-1
	//如果线程执行时没有异常,说明在getTask方法中已经对workerCount-1,
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    	//统计完成的任务数
        completedTaskCount += w.completedTasks;
        //从workers中移除,线程池当中移除了一个工作线程
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

	//根据线程池状态进行判断是否结束线程池
    tryTerminate();

    int c = ctl.get();
    //如果线程池是RUNNING或者是SHUTDOWN状态时,如果worker是异常结束,那么会直接addWorker
    //如果allowCoreThreadTimeOut=true,并且等待队列有任务,至少保留一个worker
    //如果allowCoreThreadTimeOut=false,workerCount不少于corePoolSize
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

到现在,processWorkerExit执行完之后,工作线程被销毁,以上就是整个工作线程的生命周期,从execute方法开始,Worker使用ThreadFactory创建新的工作线程,runWorker通过getTask获取任务,然后执行任务,如果getTask返回null,进入processWorkerExit方法,整个线程结束,流程图如下:

在这里插入图片描述

tryTerminate()方法
final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        /*
         * 当前线程池的状态为以下几种情况时,直接返回:
         * 1. RUNNING,因为还在运行中,不能停止;
         * 2. TIDYING或TERMINATED,因为线程池中已经没有正在运行的线程了;
         * 3. SHUTDOWN并且等待队列非空,这时要执行完workQueue中的task;
         */
        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 {
        	// 这里尝试设置状态为TIDYING,如果设置成功,则调用terminated方法
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                	// terminated方法默认什么都不做,留给子类实现
                    terminated();
                } finally {
                	// 设置状态为TERMINATED
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

interruptIdleWorkers(ONLY_ONE)的作用是因为在getTask方法中执行workQueue.take()时,如果不执行中断会一直阻塞。在下面介绍的shutdown方法中,会中断所有空闲的工作线程,如果在执行shutdown时工作线程没有空闲,然后又去调用了getTask方法,这时如果workQueue中没有任务了,调用workQueue.take()时就会一直阻塞。所以每次在工作线程结束时调用tryTerminate方法来尝试中断一个空闲工作线程,避免在队列为空时取任务一直阻塞的情况。

shutdown方法

shutdown() 方法要将线程池切换到SHUTDOWN状态,并调用interruptIdleWorkers方法请求中断所有空闲的worker,最后调用tryTerminate尝试结束线程池。

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 安全策略判断
        checkShutdownAccess();
        // 切换状态为SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 中断空闲线程
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    // 尝试结束线程池
    tryTerminate();
}

在这里思考一个问题:在runWorker方法当中,执行任务时对Worker对象w进行了lock操作,为什么要在执行任务时对每个工作线程都加锁呢???

分析一下:

  • 在getTask方法中,如果这时线程池的状态时SHUTDOWN并且workQueue为空,那么就应该返回null来结束这个工作线程,而使线程池进入SHUTDOWN状态需要调用shutdown方法。
  • shutdown方法会调用interruptIdleWrokers来中断空闲的线程,interruptIdleWrokers持有mainLock,会遍历workers来逐个判断工作线程是否空闲。但是getTask方法中没有mainLock
  • 在getTask中,如果判断当前线程池状态是RUNNING,并且阻塞队列为空,那么会调用workQueue.take() 方法进行阻塞。
  • 如果在判断当前线程池状态是RUNNING后,这时调用了shutdown方法把状态改为了SHUTDOWN,同时如果不进行中断,那么当前的工作线程在调用了workQueue.take()后会一直阻塞而不会摧毁,因为在SHUTDOWN状态下不允许再有新的任务添加到workQueue中,这样线程池就一直关闭不了
  • 上面的分析体现出,shutdown方法与getTask方法(从队列中获取任务时)存在竟态条件
  • 解决这个问题需要用到线程的中断,也就是为什么要使用interruptIdleWorkers方法。在调用workQueue.take() 时,如果发现当前线程在执行之前或者执行期间是中断状态,就会抛出InterruptedException异常,解除阻塞状态。
  • 但是要中断工作线程,还要判断工作线程是否是空闲的,如果工作线程正在处理任务,就不应该发生中断
  • 所以Worker继承自AQS,在工作线程处理任务时会进行lock,interruptIdleWrokers在进行中断时会使用tryLock来判断该工作线程是否正在处理任务,如果tryLock返回true,说明该工作线程当前未执行任务,这时才可以被中断。
interruptIdleWorkers方法
private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

interruptIdleWorkers遍历workers中所有的工作线程,若线程没有被中断并且tryLock成功,就中断该线程。为什么需要持有mainLock?因为workers是HashSet类型的,不能保证线程安全。

shutdownNow方法
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        // 中断所有工作线程,无论是否空闲
        interruptWorkers();
        // 取出队列中没有被执行的任务
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

shutdownNow方法与shutdown方法类似,不同的地方在于:

1、设置状态为STOP;
2、中断所有工作线程,无论是否是空闲的;
3、取出阻塞队列中没有被执行的任务并返回。

shutdownNow方法执行完之后调用tryTerminate方法,该方法在上文已经分析过了,目的就是使线程池的状态设置为TERMINATED。

线程池的监控

通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用

  • getTaskCount:线程池已经执行的和未执行的任务总数;
  • getCompletedTaskCount:线程池已完成的任务数量,该值小于等于taskCount;
  • getLargestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过,也就是达到了maximumPoolSize;
  • getPoolSize:线程池当前的线程数量;
  • getActiveCount:当前线程池中正在执行任务的线程数量。

通过这些方法,可以对线程池进行监控,在ThreadPoolExecutor类中提供了几个空方法,如beforeExecute方法,afterExecute方法和terminated方法,可以扩展这些方法在执行前或执行后增加一些新的操作,例如统计线程池的执行任务的时间等,可以继承自ThreadPoolExecutor来进行扩展。

总结

总体来说这篇文章有如下几个内容:

  • 分析了线程的创建,任务的提交,状态的转换以及线程池的关闭;
  • 这里通过execute方法来展开线程池的工作流程,execute方法通过corePoolSize,maximumPoolSize以及阻塞队列的大小来判断决定传入的任务应该被立即执行,还是应该添加到阻塞队列中,还是应该拒绝任务。
  • 介绍了线程池关闭时的过程,也分析了shutdown方法与getTask方法存在竞态条件;
  • 在获取任务时,要通过线程池的状态来判断应该结束工作线程还是阻塞线程等待新的任务,也解释了为什么关闭线程池时要中断工作线程以及为什么每一个worker都需要lock。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值