Java线程池ThreadPoolExecutor源码解析

目录

成员变量

构造函数

核心线程数 和 最大线程数

线程存活时间

工作队列

线程工厂

拒绝策略


它也处于JUC并发包中,是我们并发开发中的名器。

        线程池为什么出现?可以说这是一个帮我们管理程序中运行的多个线程的良好工具,这样能避免多个线程使用时的混乱,并且减少不必要的线程创建或线程销毁带来的开销。

成员变量

/*Since:
1.5
Author:
Doug Lea
*/
public class ThreadPoolExecutor extends AbstractExecutorService {
  	//int型32位来表示线程的状态(左3位)和线程数量(右29位)
		private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3; //29
    private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;//29个1

    // 线程的五种状态
    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 & ~COUNT_MASK; }
    private static int workerCountOf(int c)  { return c & COUNT_MASK; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }

    /*
     * Bit field accessors that don't require unpacking ctl.
     * These depend on the bit layout and on workerCount being never negative.
     */

    private static boolean runStateLessThan(int c, int s) {
        return c < s;
    }

    private static boolean runStateAtLeast(int c, int s) {
        return c >= s;
    }

    private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }

    /**
     * 尝试使用 CAS 递增 ctl 的 workerCount 字段。
     */
    private boolean compareAndIncrementWorkerCount(int expect) {
        return ctl.compareAndSet(expect, expect + 1);
    }

    /**
     * 尝试对 ctl 的 workerCount 字段进行 CAS 递减
     */
    private boolean compareAndDecrementWorkerCount(int expect) {
        return ctl.compareAndSet(expect, expect - 1);
    }

    /**
     减少 ctl 的 workerCount 字段。 这仅在线程突然终止时调用(请参阅 processWorkerExit)。 其他递减在 getTask 中执行。
     */
    private void decrementWorkerCount() {
        ctl.addAndGet(-1);
    }

    /**
    用于保存任务和移交给工作线程的队列。 我们不要求 workQueue.poll() 返回 null 必然意味着 workQueue.isEmpty(),所以只依赖 isEmpty 来查看队列是否为空(例如,我们在决定是否从 SHUTDOWN 转换到 TIDYING 时必须这样做) . 这适用于特殊用途的队列,例如允许 poll() 返回 null 的 DelayQueues,即使它稍后可能在延迟到期时返回非 null。
     */
    private final BlockingQueue<Runnable> workQueue;

    /**
    锁定对工人集和相关簿记的访问。 虽然我们可以使用某种并发集,但结果证明通常最好使用锁。 其中一个原因是它序列化了interruptIdleWorkers,从而避免了不必要的中断风暴,尤其是在关机期间。 否则退出线程将同时中断那些尚未中断的线程。 它还简化了largestPoolSize等一些相关的统计簿记。我们在shutdown和shutdownNow时也持有mainLock,为了保证worker set稳定,同时分别检查中断和实际中断的权限。
     */
    private final ReentrantLock mainLock = new ReentrantLock();

    /**
		 包含池中所有工作线程的集合。 仅在持有 mainLock 时访问。
     */
    private final HashSet<Worker> workers = new HashSet<>();

    /**
     * 等待条件以支持 awaitTermination。
     */
    private final Condition termination = mainLock.newCondition();

    /**
     跟踪达到的最大池大小。 只能在 mainLock 下访问
     */
    private int largestPoolSize;

    /**
     完成任务的计数器。 仅在工作线程终止时更新。 只能在 mainLock 下访问。
     */
    private long completedTaskCount;

    /*
     * All user control parameters are declared as volatiles so that
     * ongoing actions are based on freshest values, but without need
     * for locking, since no internal invariants depend on them
     * changing synchronously with respect to other actions.
     */

    /**
    新线程的工厂。 所有线程都是使用这个工厂创建的(通过方法 addWorker)。 所有调用者都必须为 addWorker 失败做好准备,这可能反映了系统或用户限制线程数的策略。 即使它不被视为错误,创建线程失败也可能导致新任务被拒绝或现有任务卡在队列中。 我们更进一步,即使在尝试创建线程时可能会抛出 OutOfMemoryError 等错误时,也保留池不变量。 由于需要在 Thread.start 中分配本机堆栈,因此此类错误相当常见,并且用户将希望执行干净池关闭以进行清理。 可能有足够的内存可供清理代码完成而不会遇到另一个 OutOfMemoryError。
     */
    private volatile ThreadFactory threadFactory;

    /**
     *在执行中饱和或关闭时调用的处理程序。
     */
    private volatile RejectedExecutionHandler handler;

    /**
     等待工作的空闲线程的超时(以纳秒为单位)。 当存在超过 corePoolSize 或允许 CoreThreadTimeOut 时,线程使用此超时。 否则他们永远等待新的工作。
     */
    private volatile long keepAliveTime;

    /**
     如果为 false(默认),核心线程即使在空闲时也保持活动状态。 如果为 true,则核心线程使用 keepAliveTime 超时等待工作。
     */
    private volatile boolean allowCoreThreadTimeOut;

    /**
		核心池大小是保持活动状态的最小工作线程数(不允许超时等),除非设置了 allowCoreThreadTimeOut,在这种情况下,最小值为零。 由于工作器计数实际上存储在 COUNT_BITS 位中,因此有效限制是corePoolSize & COUNT_MASK 。
     */
    private volatile int corePoolSize;

    /**
		最大池大小。 由于工作器计数实际上存储在 COUNT_BITS 位中,因此有效限制为maximumPoolSize & COUNT_MASK 。
     */
    private volatile int maximumPoolSize;

    /**
     * 默认拒绝执行处理程序。
     */
    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();
    
}

        源码成员变量有点多,我这里解释下重要的几个部分,这里面的ctl是一个AutomicInteger,和Integer一样占内存中32位,因为在这里线程池有五种状态(RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED)。

  • RUNNING:接受新任务并处理排队任务
  • SHUTDOWN:不接受新任务,但处理排队任务

  • STOP:不接受新任务,不处理排队任务,并中断正在进行的任务

  • TIDYING:所有任务都已终止,workerCount 为零,转换到状态 TIDYING 的线程将运行 terminate()

  • TERMINATED:terminate() 已完成。

        这些值之间的数字顺序很重要,以允许有序比较. runState 随时间单调增加,但不需要命中每个状态。 转换是:在调用 shutdown() 时: RUNNING -> SHUTDOWN ;在调用 shutdownNow() 时,(RUNNING 或 SHUTDOWN) -> STOP ;当队列和池都为空时 STOP -> TIDYING 当池为空时, SHUTDOWN -> TIDYING ; 当 terminate() 钩子方法完成时,TIDYING -> TERMINATED;在 awaitTermination() 中等待的线程将在状态达到 TERMINATED 时返回。

          ctl的前3位能表示当前线程池状态,然后其它的29位就可以表示当前线程的数量,最多2^{29}-1​个线程。

        因为ctl为AutoInteger,所以其的递增和递减操作都是原子性的。mainLock和termination为操作中所需要的锁及条件变量。关于线程池中在构造函数中需要指定的参数我们放在下面叙述。

构造函数


    /**
     使用给定的初始参数创建一个新的ThreadPoolExecutor 。
      参数:
      corePoolSize – 要保留在池中的线​​程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
      maximumPoolSize – 池中允许的最大线程数
      keepAliveTime – 当线程数大于核心数时,这是多余空闲线程在终止前等待新任务的最长时间。
      unit – keepAliveTime参数的时间单位
      workQueue – 用于在执行任务之前保存任务的队列。 这个队列将只保存execute方法提交的Runnable任务。
      threadFactory – 执行程序创建新线程时使用的工厂
      handler – 执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量
      抛出:
      IllegalArgumentException – 如果以下情况之一成立: corePoolSize < 0 keepAliveTime < 0 maximumPoolSize <= 0 maximumPoolSize < corePoolSize
      NullPointerException – 如果workQueue或threadFactory或handler为 null
     */
    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;
    }

        线程池构造函数有多个,在这我只讲参数最多的这一个。因为参数众多,我决定分开来讲。

核心线程数 和 最大线程数


    /**
    核心池大小是保持活动状态的最小工作线程数(不允许超时等),除非设置了 allowCoreThreadTimeOut,在这种情况下,最小值为零。 由于工作器计数实际上存储在 COUNT_BITS 位中,因此有效限制是corePoolSize & COUNT_MASK 。
     */
    private volatile int corePoolSize;

    /**
     * Maximum pool size.
			最大池大小。 由于工作器计数实际上存储在 COUNT_BITS 位中,因此有效限制为maximumPoolSize & COUNT_MASK 。
     */
    private volatile int maximumPoolSize;	

    /**

        这里核心线程数和最大线程数的概念如下:我们假设核心线程数为x,最大线程数为y(一定有y>=x)。对于 线程工厂是否创造线程,已当前线程数目为​x_1,会有下面的判断模式。

  1. 首先判断与核心线程数x的关系,若x_1<x​,则直接创建线程,执行任务,并将​x_1=x_1+1。若x_1 \geq x ​则调到第2步。
  2. 考虑当前的 工作队列 的容量,若当前工作线程容量不满,则创建线程,塞入工作队列中。若工作队列容量已满,则进入第3步。
  3. 把当前线程数量与最大线程数y比较,若x_1\leq y​,则工厂创建非核心线程执行任务,x_1=x_1+1​。否则进入第4步。
  4. 若​x_1==y,则工厂不能为其创建线程,需要执行 拒绝策略

        这里关于 线程工厂、工作队列、拒绝策略放到后面来讲。这里先说下一般如何设置线程中的核心线程数和最大线程数。

在java中我们可以这样获得我们计算机的CPU数目。

 int CPU_COUNT = Runtime.getRuntime().availableProcessors();

比如在我的机器上6核12线程就是12(代表最多同时12个线程运行)。

  • 对于CPU密集型,为了提高效率,希望减少线程任务的切换,因此设置CPU_COUNT即可。不过通常把线程的数量设置为CPU 核数 +1,会实现最优的利用率。即使当密集型的线程由于偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费,从而保证 CPU 的利用率。

  • 对于 IO 密集型任务最大线程数一般会大于 CPU 核心数很多倍,因为 IO 读写速度相比于 CPU 的速度而言是比较慢的,如果我们设置过少的线程数,就可能导致 CPU 资源的浪费。而如果我们设置更多的线程数,那么当一部分线程正在等待 IO 的时候,它们此时并不需要 CPU 来计算,那么另外的线程便可以利用 CPU 去执行其他的任务,互不影响,这样的话在任务队列中等待的任务就会减少,可以更好地利用资源。这里根据《Java并发编程实战》推荐这么设置:线程数 = CPU 核心数 * (1 + IO 耗时/ CPU 耗时)。

线程存活时间

 /**
    等待工作的空闲线程的超时(以纳秒为单位)。 当存在超过 corePoolSize 或允许 CoreThreadTimeOut 时,线程使用此超时。 否则他们永远等待新的工作。
     */
    private volatile long keepAliveTime;


/*
    如果此池允许核心线程超时并在 keepAlive 时间内没有任务到达时终止,则返回 true,并在新任务到达时根据需要进行替换。 如果为真,则适用于非核心线程的相同保活策略也适用于核心线程。 当为 false(默认值)时,核心线程永远不会因缺少传入任务而终止。
    返回:
    如果允许核心线程超时,则为true ,否则为false
    自从:
    1.6
     *
     * @since 1.6
     */
    public boolean allowsCoreThreadTimeOut() {
        return allowCoreThreadTimeOut;
    }

         一般来说,这个keepAliveTime是针对非核心线程的,当某个非核心线程任务结束后等待keepAliveTime*时间单位后仍未被指派执行任务,则会被销毁,当前线程数x_1=x_1-1​。另外如果allowCoreThreadTimeOut成员变量被置为true时,那么核心线程在等待keepAliveTime时间单位后仍未被指派执行任务,也会被销毁。

工作队列

这里使用的是BlockingQueue接口,继承自Queue。

public interface BlockingQueue<E> extends Queue<E> {
	...
}
/**
其中实现类有
ArrayBlockingQueue。 //有界队列
DelayQueue
LinkedBlockingDeque 无界队列
PriorityBlockingQueue //无界优先队列
SynchronousQueue //同步移交队列
*/

        其中常用的是ArrayBlockingQueue、PriorityBlockingQueue、LinkedBlockingDeque和SynchronousQueue,它们常作为工作队列。篇幅原因,暂不分析其源码,简单说明下。

  • ArrayBlockingQueue:有界阻塞队列,需指定容量,可公平也可不公平,公平则FIFO,非公平则底层用的非公平锁(可能会考虑事件执行时间占比)。

  • PriorityBlockingQueue:优先级阻塞队列(无界的),类似于PriorityQueue。不过其是阻塞的,因为是无界的,其实不会阻塞。。

  • LinkedBlockingDeque:典型的无界队列。

  • SynchronousQueue:同步移交队列,其容量为0。即你想从队列拿线程时,必须有个人过来放线程,不然会认为线程阻塞则去看最大线程数了。

线程工厂

线程工厂(ThreadFactory)其实就是一个接口。

public interface ThreadFactory {

    /**
    构造一个新的Thread 。 实现还可以初始化优先级、名称、守护进程状态、 ThreadGroup等。
    参数:
    r - 由新线程实例执行的可运行对象
    返回:
    构造的线程,如果创建线程的请求被拒绝,则为null
     */
    Thread newThread(Runnable r);
}

        在这里你可以自己实现线程创建工厂类。比如newThread方法规定线程创建的规则:线程名,线程组等,便于统一管理。

拒绝策略

拒绝策略就是线程池无法为请求方创建新线程执行任务时所作出的响应,有四种。

  1. AbortPolicy:默认的策略,即抛出一个异常。

  2. CallerRunsPolicy:在调用中的线程中直接运行(比如main线程)。

  3. DiscardOldestPolicy:尝试弹出工作队列中的第一个线程,然后执行目标任务。

  4. DiscardPolicy:什么都不做,不返回也不抛出异常。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值