大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)

  • 参考博客:
    点击!!!!!点击!!!!!点击!!!!!点击!!!!!点击!!!!!点击!!!!!延迟队列详情点击!!主要参考,点击!!!!!

  • 为什么提出线程池?
    • 什么是线程池技术?
    • 什么时候用线程池计数?
    • 线程池的优势:
    • 线程池的设计思路及执行流程:
  • 线程池的结构
    • 线程池之任务的实现方式:
    • 浅谈线程池ThreadPoolThread:
      • ThreadPoolThread的属性:
      • ThreadPoolThread的构造方法:
        • 七个小矮人之第五个小矮人:任务队列
        • 七个小矮人之第六个小矮人:线程工厂
        • 七个小矮人之第七个小矮人:拒绝策略
      • ThreadPoolThread方法:
        • ThreadPoolThread方法之线程初始化:
        • ThreadPoolThread`方法之线程池关闭:
        • ThreadPoolThread`方法之线程容量调整:
        • ThreadPoolThread`方法之线程池监控:
      • 线程池ThreadPoolThread使用:
    • Executors封装的四种常见的线程池
  • 线程池源码级分析
    • ThreadPoolThread源码解析之execute():
    • ThreadPoolThread源码解析之addWorker():
    • ThreadPoolThread内部类Worker源码解析:
    • ThreadPoolThread源码解析之runWorker():
    • ThreadPoolThread源码解析之getTask():
    • ThreadPoolThread源码解析之processWorkerExit():
    • AbstractExecutorService源码解析之Callable任务带来的异常不输出问题:
  • 到底就结束了对线程池的相关学习,感谢各位大佬的博客借鉴。

为什么提出线程池?

  • 其实很多设计的产生,都源自生活需要,有一句话说的好:科技发展不是因为懒,而是为了节约更多的时间高效率的做更多工作。线程池的产生,也是如此。
  • 在学习的过程中,线程的重要性不言而喻。有时我们需要多次使用线程,既然用到线程,我们就要创建并且销毁线程,而这个创建和销毁的过程都需要映射到操作系统,并且会消耗一定的内存,而内存资源是非常宝贵的,因此其代价是比较高昂的,所以我们就想是否可以不要每次都创建和销毁线程,这时,线程池应运而生。
  • 我们举一个开发过程中的例子:
    • 假设一个服务器一天要处理20000个请求,并且每个请求需要一个单独的线程完成,而每个线程执行的时间非常短,这样就会频繁的创建和销毁线程,这样就大大降低了系统的效率,并且可能出现服务器在为每个请求创建新线程和销毁线程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多。
    • 那么有没有一种办法使执行完一个任务,并不被销毁,而是可以继续执行其他的任务呢?
    • 这就是线程池的目的了。线程池为线程生命周期的开销和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。

什么是线程池技术?

  • “线程池”,顾名思义就是一个线程缓存,线程是稀缺资源,如果被无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,因此Java中提供线程池对线程进行统一分配、调优和监控;

什么时候用线程池计数?

  • 单个任务处理时间比较短;
  • 需要处理的任务量很大;

线程池的优势:

  • 降低资源消耗: 线程池通常会维护一些线程(数量为 corePoolSize),这些线程被重复使用来执行不同的任务,任务完成后不会销毁。在待处理任务量很大的时候,通过对线程资源的复用,避免了线程的频繁创建与销毁,从而降低了系统资源消耗。
  • 提高响应速度: 由于线程池维护了一批 alive 状态的线程,当任务到达时,不需要再创建线程,而是直接由这些线程去执行任务,从而减少了任务的等待时间。
  • 提高线程的可管理性: 使用线程池可以对线程进行统一的分配,调优和监控

线程池的设计思路及执行流程:

  • 我们通过一个工厂的设计流程来对比线程池(图和思路来自另一篇博客,文章头有引用地址):
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第1张图片
  • 通过图我们可以这样进行对比:
    • 工厂流程: 工厂中有固定的一批工人,我们称之为正式员工,一般不会对他们进行裁员,并且大多数订单也是由他们完成,但是如果赶上"双11",那么订单量就会暴涨,我们虽然可以先保存一些订单,但是如果订单量过大,那么可能时间上来不及,这时候我们就要雇佣一些临时工来完成暴增的订单,如果这样还不够,那么我们要认同割爱,放其一些订单了。
      • 总而言之分为4步:
      • 订单1:这一部分由正式工完成; -------------》正式工对应核心线程
      • 订单2:这一部分新增的订单存放在仓库; -------------》临时工对应普通线程
      • 订单3:如果仓库都放满了,这时候要雇佣临时来完成订单; -------------》仓库对应任务队列
      • 订单4:订单太多,不要了;-------------》不要了对应拒绝策列
    • 线程池流程:
      • 任务进来时,首先执行判断,判断当前线程数量(worker)是否小于核心线程(corePoolSize),如果是,创建核心线程并执行任务;
      • 如果不是,则判断任务队列(workQueue)中是否有地方存放该任务,如果有,就将任务保存在任务队列中,等待执行;
      • 如果任务队列满了,在判断当前线程数是否小于最大线程数(maximumPoolSize),如果没有超出这个数量,就创建普通线程(worker)执行任务;
      • 如果超出了,就调用handler实现拒绝策略(RejectedExecutionHandler);
    • 我们对上面的4步做一个流程图来进一步理解线程池:
      大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第2张图片
  • 大体是以上四步,但是还有一些细节需要我们知道,比如调度员对应getTask(),订单对应任务(Runnable),工厂对应线程池,拒绝策略有哪几种,任务队列有哪几种,后面都会有详细的讲解;

线程池的结构

  • 我们先看一下Java线程池框架体系:
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第3张图片
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第4张图片
  • 通过这两张图我们可以发现,Executor下有一个重要子接口ExecutorService,其中定义了线程池的具体行为:在这里我们先提几个:
    • execute(Runnable command): 执行Ruannable接口实现的任务;
    • submit(task): 可用来提交CallableRunnable任务,并返回代表此任务的Future对象,这是平时学习不太用到的;
    • shutdown(): 使线程池进入shutdown状态,不再接收新任务,但是可以处理任务队列中的任务;
    • shutdownNow(): 使线程池进入shutdownNow状态,什么任务也不执行了;

线程池之任务的实现方式:

  • 我们把任务通过两个接口实现,以有无返回值为区分点,如下:
Runnable,Thread,Callable
 
// 实现Runnable接口的类将被Thread执行,表示一个基本的任务,这个相信不用多说了
 
public interface Runnable {
     
 
public abstract void run();
 
}
//Callable同样是任务,与Runnable接口不同的是:它可以接收泛型,并且执行任务后有返回值
 
public interface Callable<V> {
     
 
// 相对于run方法的带有返回值的call方法
 
V call() throws Exception;
 
}

浅谈线程池ThreadPoolThread:

  • 我们首先看一下ThreadPoolThread的属性以及构造方法,因为我们可以通过这个ThreadPoolThread来自主设计线程池;

ThreadPoolThread的属性:

	//这个属性是用来存放当前运行的		worker数量、线程池状态
	//int是32位的,这里把int的高3位拿来充当线程池状态的标志位,低29位拿来充当当前运行worker的数量
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //worker可以存放的位数
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //worker可以存放的最大数,536,870,911
    private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
    // runState is stored in the high-order bits 线程池的不同状态
  • ctl: 是对线程池的运行状态线程数量进行控制的一个字段,具体怎么表示看上述源码中的注释就好了;
    //当创建线程池后,初始时,线程池处于RUNNING状态,能够接收新任务,以及对同步队列中的任务进行处理
    private static final int RUNNING    = -1 << COUNT_BITS;//高3位为111 
    //如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,但它会处理同步队列中的任务
    private static final int SHUTDOWN   =  0 << COUNT_BITS;  //高3位为000
    //如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在处理的任务
    private static final int STOP       =  1 << COUNT_BITS;//高3位为001
    private static final int TIDYING    =  2 << COUNT_BITS;//高3位为010
   //当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态
    private static final int TERMINATED =  3 << COUNT_BITS;//高3位为011
  • 我们通过一张图来进一步描述上面5种状态:
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第5张图片
    //存放任务的任务(阻塞)队列
    private final BlockingQueue<Runnable> workQueue;
    //锁,但是注意不是重入锁
    private final ReentrantLock mainLock = new ReentrantLock(); 
    //worker的集合,用set来存放
    private final HashSet<Worker> workers = new HashSet<>();
    //历史达到的worker数最大值
    private int largestPoolSize;
    //已经完成任务的线程数
    private long completedTaskCount;
    //线程工厂
    private volatile ThreadFactory threadFactory;
    //当队列满了并且worker的数量达到maximumPoolSize的时候,执行具体的拒绝策略
    private volatile RejectedExecutionHandler handler;
    //超出coreSize的worker的生存时间,即普通线程的生存时间
    private volatile long keepAliveTime;
    //是否允许核心线程能回收
    private volatile boolean allowCoreThreadTimeOut;
    //核心线程的数量
    private volatile int corePoolSize;
    //线程池的线程数量(核心线程+普通线程)
    private volatile int maximumPoolSize;

ThreadPoolThread的构造方法:

  • ThreadPoolThread提供了四个构造方法:
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第6张图片
    //一共有7个参数,第六个和第七个不设置,用默认值
    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;
    }
  • 我们首先看一下构造线程池的七个"小矮人":
    • corePoolSize(必需): 核心线程数,即池中一直保持存活的线程数,即使这些线程处于空闲。但是将allowCoreThreadTimeOut参数设置为true后,核心线程处于空闲一段时间以上,也会被回收;
    • maximumPoolSize(必需): 池中允许的最大线程数,当核心线程全部繁忙且任务队列存满之后,线程池会临时追加线程,直到总线程数达到maximumPoolSize这个上限;
    • keepAliveTime(必需): 线程空闲超时时间,当普通线程处于空闲状态的时间超过这个时间后,该线程将被回收。将allowCoreThreadTimeOut参数设置为true后,核心线程也会被回收;
    • unit(必需): keepAliveTime参数的时间单位。
      • TimeUnit.DAYS(天)、TimeUnit.HOURS(小时)、TimeUnit.MINUTES(分钟)
      • TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)、TimeUnit.MICROSECONDS(微秒)、TimeUnit.NANOSECONDS(纳秒)
    • workQueue(必需): 任务队列,采用阻塞队列实现。当核心线程全部繁忙时,由execute方法提交的Runnable任务存放到任务队列中,等待被核心线程处理(当然还有submit方法);
    • 下面就是2个非必须的“小矮人”:
    • ThreadFactory(可选)线程工厂。指定线程池创建线程的方式;
    • handler(可选): 拒绝策略,当线程池中线程数达到maximumPoolSizeworkQueue装满,后续提交的任务将被拒绝,handler可以指定用什么方式拒绝任务;
      • 注意:可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略

七个小矮人之第五个小矮人:任务队列

  • 使用ThreadPoolExecutor需要指定一个实现了BlockingQueue接口的任务队列;
  • ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们分别是:
    • SynchronousQueue: 同步队列,该队列没有容量,每一个任务的插入都需要等待一个任务删除操作;
      • 注意:如果使用了SynchronousQueue队列,通常都需要一个很大的maximumPoolSize,否则就很容易进入拒绝策略。
    • LinkedBlockingQueue:无界任务队列,该阻塞队列的容量可以无限大(Integer.MAX_VALUE),直到内存耗尽 (OOM) 为止;
      • 基于链表结构,因为任务队列相当于无限大,所以就不需要在线程池中添加普通线程,这样就提高了线程池的吞吐量,但代价就是牺牲了内存空间,甚至OOM;
      • 不过使用这个任务队列可以指定容量,这样就相当于有界队列了;
    • ArrayBlockingQueue:有界任务队列
      • 基于数组结构,在线程池初始化时,指定队列的容量,后续无法再调整。这种有界队列有利于防止资源耗尽,但可能更难调整和控制;
  • Java还提供了另外4种任务队列:
    • PriorityBlockingQueue:优先任务队列,放在PriorityBlockingQueue中的元素必须实现Comparable接口,这样才能通过实现compareTo()方法进行排序:
      • 优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置;
      • 对于任务执行的先后顺序ArrayBlockingQueueLinkedBlockingQueue都是先进先出的原则;
      • 而对于PriorityBlockingQueue则是可以自定义优先级,从而更加灵活的控制线程的执行顺序;
    • DelayQueue:延迟队列,基于二叉堆实现,同时具备:无界队列、阻塞队列、优先队列的特征。DelayQueue延迟队列中存放的对象,必须是实现Delayed接口的类对象。通过执行时延从队列中提取任务,时间没到任务取不出来
    • LinkedBlockingDeque:双端队列
      • 基于链表结构,既可以从尾部插入/取出任务,还可以从头部插入/任务;
      • 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出);
    • LinkedTransferQueue: 无界阻塞队列
      • 基于链表结构,这个队列采用一种预占模式
      • 预占模式(生产者消费者模式):
        • 消费者线程取元素时,如果队列不为空,则直接取走数据;
        • 若队列为空,那就生成一个空节点入队,然后消费者线程被等待在这个节点上;
        • 后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素;

七个小矮人之第六个小矮人:线程工厂

  • 对于线程工厂(两点):
    • 上面的参数介绍已经说过,线程工厂是为了指定线程创建的方式,Executors类已经为我们非常贴心地提供了一个默认的线程工厂;
    • 但是有时候默认的线程工厂并不是我们所需要的,比如在使用线程池的过程中,我们需要在日志中打出可以方便让人理解的线程信息,那么重写ThreadFactory是一个不二的选择;
	package java.util.concurrent;    //线程工厂接口
	public interface ThreadFactory {
     
		    Thread newThread(Runnable r);
		}
              
  	 //默认的线程工厂
    private static class DefaultThreadFactory implements ThreadFactory {
        
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
          //这里一般重写
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
        }
		//重写的时候一般这里不用动
        public Thread newThread(Runnable r) {
     
            Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement() , 0);
            if (t.isDaemon())      //线程不能是守护线程
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)   //线程的优先级设置成5
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
              
  //重写的线程工厂
	package com.wwj.threadpoolexecutorDemos;

	import java.util.concurrent.ThreadFactory;
	import java.util.concurrent.atomic.AtomicInteger;

	public class ThreadFactoryDemo implements ThreadFactory {
     
	    private static final AtomicInteger poolNumber = new AtomicInteger(1);
	    private final ThreadGroup group ;
	    private final AtomicInteger threadNumber = new AtomicInteger(1);
	    private final String namePrefix;
	    /** 新加的:模块名 */
	    private String moduleName;
	    /** 新加的:功能名 */
	    private String functionName;
	
	    public ThreadFactoryDemo(String moduleName , String functionName){
     
	        this.moduleName = moduleName == null ? "" : moduleName;
	        this.functionName = functionName == null ? "" : functionName;
	        SecurityManager s = System.getSecurityManager();
	        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
	        // 在线程名上加上模块名标示,方便调试
	        namePrefix = this.moduleName + "-" + this.functionName + "-pool-" + poolNumber.getAndIncrement() + "-thread-";
	    }
	    @Override
	    public Thread newThread(Runnable r) {
     
	        Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
	        if (t.isDaemon())
	            t.setDaemon(false);
	        if (t.getPriority() != Thread.NORM_PRIORITY)
	            t.setPriority(Thread.NORM_PRIORITY);
	        return t;
	    }
	
	} 

七个小矮人之第七个小矮人:拒绝策略

  • 拒绝策略是当系统超负荷运转的时候,有可能出现的情况;具体是哪一步发生我们再看流程图:
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第7张图片
  • JDK本身提供了4种内置的拒绝策略:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy和DiscardPolicy,下面分别介绍这几个策略:
    • AbortPolicy(默认): 直接抛出异常RejectedExecutionException,默认策略;
    • DiscardPolicy: 直接丢弃任务,不抛出异常;
    • DisCardOldSetPolicy: 丢弃同步队列中靠最前的任务,并执行当前任务;
    • CallerRunsPolicy: 交由任务的调用线程来执行当前任务;
  • 除了上面的四种拒绝策略,还可以通过RejectedExecutionHandler接口,实现自定义的拒绝策略
  • 源码:
package java.util.concurrent;
public interface RejectedExecutionHandler {
     
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
  • 举例:
package com.wwj.threadpoolexecutorDemos;

import com.wwj.lockdemos.ReentrantLockReadWriteLockDemo;

import java.util.concurrent.*;

public class RejectedExecutionHandleDemo implements Runnable {
     
    @Override
    public void run() {
     
        System.out.println(Thread.currentThread()+"Thread:ID:"+Thread.currentThread().getId());
        try {
     
            Thread.sleep(1000);
        }catch (InterruptedException e){
     
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
     
        RejectedExecutionHandleDemo rejectedExecutionHandleDemo = new RejectedExecutionHandleDemo();
        ExecutorService exec = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10),
                Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
     
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
     
                System.out.println(r.toString()+"is discard");
            }
        });
        for(int i=0 ; i<Integer.MAX_VALUE;i++){
     
            exec.submit(rejectedExecutionHandleDemo);
            Thread.sleep(10);
        }
    }
}
/*结果:
Thread[pool-1-thread-1,5,main]Thread:ID:15
Thread[pool-1-thread-2,5,main]Thread:ID:16
Thread[pool-1-thread-3,5,main]Thread:ID:17
Thread[pool-1-thread-4,5,main]Thread:ID:18
Thread[pool-1-thread-5,5,main]Thread:ID:19
java.util.concurrent.FutureTask@7530d0a[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@27973e9b[Wrapped task = com.wwj.threadpoolexecutorDemos.RejectedExecutionHandleDemo@312b1dae]]is discard
.........
*/
  • 通过结果我们可以发现:每有5个线程执行,就会有相应的拒绝策略,然后有对应的输出。

ThreadPoolThread方法:

ThreadPoolThread方法之线程初始化:

  • 默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程;
    • 在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
      • prestartCoreThread(): boolean prestartCoreThread(),初始化一个核心线程;
      • prestartAllCoreThreads(): int prestartAllCoreThreads(),初始化所有核心线程,并返回初始化的线程数;
      • 源码:
/*
创建一个核心线程,注意:这会覆盖启动核心线程的默认策略
*/
public boolean prestartCoreThread() {
     
	return workerCountOf(ctl.get()) < corePoolSize && addWorker(null, true); //可以看完addWorker再来
 }
public int prestartAllCoreThreads() {
     
	int n = 0;
	while (addWorker(null, true))
		++n;
		return n;
 }

ThreadPoolThread`方法之线程池关闭:

  • ThreadPoolExecutor提供了两个方法,用于线程池的关闭;
    • shutdown(): 不会立即终止线程池,而是要等所有任务队列中的任务都执行完后才终止,但再也不会接受新的任务;
    • shutdownNow(): 立即终止线程池,并尝试打断正在执行的任务,并且清空任务队列,返回尚未执行的任务;
    public void shutdown() {
     
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
     
      	    //检查是否可以关闭线程
            checkShutdownAccess();
             //设置线程池状态
            advanceRunState(SHUTDOWN);
             //尝试中断worker
            interruptIdleWorkers();
            //预留方法,留给子类实现
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
     
            mainLock.unlock();
        }
        tryTerminate();
    }
    
    private void interruptIdleWorkers() {
     
        interruptIdleWorkers(false);
	}
	private void interruptIdleWorkers(boolean onlyOne) {
     
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
     
            //遍历所有的worker
            for (Worker w : workers) {
     
                Thread t = w.thread;
                //先尝试调用w.tryLock(),如果获取到锁,就说明worker是空闲的,就可以直接中断它
                //注意的是,worker自己本身实现了AQS同步框架,然后实现的类似锁的功能
                //它实现的锁是不可重入的,所以如果worker在执行任务的时候,会先进行加锁,这里tryLock()就会返回false
                if (!t.isInterrupted() && w.tryLock()) {
     
                    try {
     
                        t.interrupt();
                    } catch (SecurityException ignore) {
     
                    } finally {
     
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
     
            mainLock.unlock();
        }
}
            
       public List<Runnable> shutdownNow() {
     
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
     
            checkShutdownAccess();
            //检测权限,这里设置的是STOP
            advanceRunState(STOP);
            //中断所有的worker
            interruptWorkers();  
             //清空任务队列
            tasks = drainQueue();    //这也是和shutdown() 的不同
        } finally {
     
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
private void interruptWorkers() {
     
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
     
            //遍历所有worker,然后调用中断方法
            for (Worker w : workers)
                w.interruptIfStarted();   //与shutdown()不同的是,shutdownNow()是直接中断,而不是interrupt()
                   } finally {
     
            mainLock.unlock();
        }
}

ThreadPoolThread`方法之线程容量调整:

  • ThreadPoolExecutor提供了动态调整线程池容量大小的方法:
    • setCorePoolSize: 设置核心池大小;
    • setMaximumPoolSize: 设置线程池最大能创建的线程数目大小;

ThreadPoolThread`方法之线程池监控:

public long getTaskCount() //线程池已执行与未执行的任务总数
public long getCompletedTaskCount() //已完成的任务数
public int getPoolSize() //线程池当前的线程数
public int getActiveCount() //线程池中正在执行任务的线程数量

线程池ThreadPoolThread使用:

  • 对于ThreadPoolExecutor本身,我们也可以对自定义的线性池进一步编译其行为,这需要重写下面几个方法(具体为什么要重写这几个方法后面会分析):
protected void afterExecute(Runnable r, Throwable t) {
      }
protected void terminated() {
      }
protected void terminated() {
      }
  • 我们通过一个程序的对比来发现这三个方法的作用:
package com.wwj.threadpoolexecutorDemos;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ExecutorPoolExecutorDemo1 implements Runnable {
     
    private String name;

    public ExecutorPoolExecutorDemo1(String name){
     
        this.name = name;
    }
    @Override
    public void run() {
     
        System.out.println("正在执行:Thread ID :" + Thread.currentThread().getId() + " Nmae = " + Thread.currentThread().getName());
    }

    public static void main(String[] args) throws  InterruptedException{
     
        ExecutorService exec = new ThreadPoolExecutor(2,2,0l, TimeUnit.SECONDS,new LinkedBlockingDeque<>()){
     
            @Override
            protected void beforeExecute(Thread t, Runnable r) {
     
                super.beforeExecute(t, r);
                System.out.println("准备执行" + ((ExecutorPoolExecutorDemo1)r).name );
            }

            @Override
            protected void afterExecute(Runnable r, Throwable t) {
     
                super.afterExecute(r, t);
                System.out.println("执行完成:" + ((ExecutorPoolExecutorDemo1)r).name);
            }

            @Override
            protected void terminated() {
     
                super.terminated();
                System.out.println("线程池退出");
            }
        };
            for(int i=0 ; i<2 ; i++){
     
                ExecutorPoolExecutorDemo1 executorPoolExecutorDemo1 = new ExecutorPoolExecutorDemo1("wwj-"+1);
                exec.execute(executorPoolExecutorDemo1);
                Thread.sleep(10);
            }
            exec.shutdown();
    }
}
/*结果:
准备执行wwj-1
准备执行wwj-1
正在执行:Thread ID :16 Nmae = pool-1-thread-2
正在执行:Thread ID :15 Nmae = pool-1-thread-1
执行完成:wwj-1
执行完成:wwj-1
线程池退出
*/
  • 通过结果我们发现,我们输出有更详细的信息,也有助于我们排查错误,JDK中也标注这几个方法可以由用户自行实现,至于为什么,具体逻辑体系 后面讲解;
  • 注意: 当我们在运用线性池的时候,一定要注意在任务执行的run()方法的最外层,一定要抓住所有异常,否则线程内部抛出的异常会被吞噬,具体逻辑后面讲解;

Executors封装的四种常见的线程池

  • 上面我们已经分析了通过ThreadPoolExecutor来自行定义线程池,但是有时候没有必要去自行定义,完全可以用现成的线程池,
  • 很多大厂不建议频繁使用这几种线程池,但是我们可以学习其封装细节及思路!
  • 那让我们看一看这四种线程池:
  • newFixedThreadPool:
    • newFixedThreadExecutor(n):固定容量的线程池,无普通线程;
    • 每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入任务队列直到前面的任务完成才继续执行;
    • 任务队列未指定容量,代表使用默认值Integer.MAX_VALUE
    • 适用于:需要控制并发编程的场景
	//使用默认的线程工厂
public static ExecutorService newFixedThreadPool(int nThreads) {
     
	return new ThreadPoolExecutor(nThreads, nThreads , 0L, TimeUnit.MILLISECONDS,
			 new LinkedBlockingQueue<Runnable>());
    }
	//使用自定义的线程工厂
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
     
	return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
			new LinkedBlockingQueue<Runnable>(),threadFactory);
    }
  • newSingleThreadExecutor:
    • 线程池有且只有一条线程(核心线程), 因此只有一条线程来执行任务;
    • 任务队列未指定容量,代表使用默认值Integer.MAX_VALUE
    • 适用于:有顺序的任务的应用场景;
    public static ExecutorService newSingleThreadExecutor() {
     
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS , new LinkedBlockingQueue<Runnable>()));
  • newScheduleThreadExecutor:
  • 定时线程池。指定核心线程数量,普通线程数量无限,线程执行完任务立即回收,任务队列为延时阻塞队列;
  • 这是一个比较特别的线程池,适用于执行定时或周期性的任务;
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
     
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
                              
	// 继承了 ThreadPoolExecutor 
	public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {
     
	 // 构造函数,省略了自定义线程工厂的构造函数
	    public ScheduledThreadPoolExecutor(int corePoolSize) {
       //DEFAULT_KEEPALIVE_MILLIS=10L
	      super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, 	new DelayedWorkQueue());
	}
		// 延时执行任务
	public ScheduledFuture<?> schedule(Runnable command , long delay , TimeUnit unit) {
     
        ...
    }
	// 定时执行任务
  public ScheduledFuture<?> scheduleAtFixedRate(Runnable command , long initialDelay , long period , TimeUnit unit) 
   {
     ...}
}
  • 应用:
package com.wwj.threadpoolexecutorDemos;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduleThreadPoolDemo {
     
    public static void main(String[] args) {
     
        ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        Runnable task = new Runnable(){
     
            public void run() {
     
                System.out.println(Thread.currentThread().getName() + "--->运行");
            }
        };
      ((ScheduledExecutorService) scheduledThreadPool).schedule(task, 2, TimeUnit.SECONDS); // 延迟2s后执行任务
        ((ScheduledExecutorService) scheduledThreadPool).scheduleAtFixedRate(task,50,2000,TimeUnit.MILLISECONDS);// 延迟50ms后、每隔2000ms执行任务
    }
}
  • newCacheThreadExecutor(推荐使用):
    • 可缓存线程池,该线程池中没有核心线程,普通线程的数量为Integer.max_value,当有需要时创建线程来执行任务,没有需要时则回收线程;
    • 适用于:耗时少(这时候线程基本都能保证不是空闲状态,没必要销毁),任务量大的情况;
  public static ExecutorService newCachedThreadPool() {
     
      return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
  }
  • 线程池大小: 最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 ) CPU数目*

线程池源码级分析

  • 在分析前,我们先通过一个图来看一下线程池是如何运行的:
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第8张图片
  • 我们先看execute方法;

ThreadPoolThread源码解析之execute():

  • ThreadPoolExecutor类中,任务提交方法的入口是execute(Runnable command)方法(submit()方法本质也是调用了execute(),只不过 对任务进行了包装,后面会分析);
  • 源码详读:
	 /* 
	 * 在将来的某个时候执行给定的任务。
	 * 任务可以在新线程中执行,也可以在现有的池线程中执行。
	 * 如果由于此执行器已关闭或已达到其容量而无法提交任务以供执行,则由当前的{@code RejectedExecutionHandler}处理该任务。
	 * @param command the task to execute  待执行的任务命令 
	 */
    public void execute(Runnable command) {
     
    	//任务为空,则不需要执行,抛出空指令异常
        if (command == null)
            throw new NullPointerException();		
		/*
		private static final int COUNT_BITS = Integer.SIZE - 3;  3为32位的高3位, 表示线程池状态, COUNT_BITS=32-3=29
		private static final int RUNNING  = -1 << COUNT_BITS;    
			-1--> 11111111111111111111111111111111 ---> 左移29位===》RUNNING= 11100000000000000000000000000000  
		private static int ctlOf(int rs, int wc) { return rs | wc; }   二进制“或”比较
		private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 
			RUNNING= 11100000000000000000000000000000  0= 00000000000000000000000000000000
			ctlOf(RUNNING, 0) ==》 ct1=11100000000000000000000000000000 
		*/
        int c = ctl.get();   c保存两部分:1、工作线程数(低29位)2、线程池的状态(高3位)
     /*
     private static final int COUNT_MASK = (1 << COUNT_BITS) - 1; ===》 COUNT_MASK=00011111111111111111111111111111
     private static int workerCountOf(int c)  { return c & COUNT_MASK; }
     由此我们可以通过,workerCountOf方法取得是c的29位的值,也就是工作线程数
     */     
步骤1:当工作线程数<核心线程数,则通过addWorker看能否加入核心线程,失败则步骤2
        if (workerCountOf(c) < corePoolSize) {
     
        /*
       	 两个参数:
       	 	command代表要执行的任务;
       	 	true代表使用coreSize作为边界,否则使用maximumPoolSize
        */
        创建一个核心线程并执行任务,成功则返回,因为不用入队了,否则进入步骤2
            if (addWorker(command, true))
                return;
            c = ctl.get();  //重新获取c,防止在这期间线程池的状态变了
        }
        	总而言之,当工作线程>=核心线程数 或者addWorker失败,则进入步骤2
        	步骤2是关于入任务队列的操作,这是很重要的,因为线程池状态可能在变,需要反复校验,如下:
 /*
步骤2:
	1、校验线程池是否是Running状态且任务是否能成功加入到workQueue(任务队列),成功则等待执行该任务,失败则步骤3 	
	如果状态都不能满足就直接步骤3
*/
        if (isRunning(c) && workQueue.offer(command)) {
     
        //如果入了任务队列,这时需要再次对线程池状态校验(因为入队也需要时间,防止这个期间线程池状态变了):
            int recheck = ctl.get();
        //如果线程池非Running且从任务队列中移除任务成功,则拒绝该任务
            if (! isRunning(recheck) && remove(command))
                reject(command);
    //如果上面if判断线程池是Running状态或者已经不是Running状态但从任务队列中移除不了,则判断当前有没有线程,没有则创建,
    //目的是为了保证线程池有线程能执行任务
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
步骤3:如果线程池不是Running状态或任务入列失败,通过addWorker看能否创建普通线程来执行任务,失败则步骤4
        else if (!addWorker(command, false))
步骤4:执行失败策略
            reject(command);
    }
  • 通过源码我们可以发现:该方法其实只在尝试做两件事:能否入队➕能否创建线程执行任务
  • 我们简述一遍其流程:
    • 第一步: 通过 ctl.get()、workerCountOf得到包含线程池线程数的值,然后判断是否小于核心线程数,如果小于,则在线程池中创建线程,让其执行任务,否则进入第二步;
      • 注意:如果小于,则在线程池中创建核心线程,不过如果创建失败,同样也需要进入步骤二,但是因为创建线程花费了时间,所以要通过ctl.get()获得最新的线程池状态和线程池中的线程数。
    • 第二步: 当第一步不满足时,就准备进入任务队列,但是要看当前任务队列的状态。所以,我们需要先判断线程池是否处于Running状态(只有Running状态的线程池才可以接受新任务);如果不是Running状态则进入第四步,如果是Running状态且任务添加到队列成功则进入第三步,失败也进入第四步;
    • 第三步: 至此,说明我们已经判断线程池是Running状态,并且入列成功,但是这入列也花费了时间(虽然不多),因此为了保险,我们需要重新判断线程池状态:
      • 如果线程池是Running状态,或者线程池不是Running状态但无奈不能在队列中去除刚刚加入的任务(可能已被某线程获取?),那只能去等待执行,与此同时我们需要判断线程池中工作线程是否为0,因为要保证线程池中有空闲线程可以执行任务;
        • 如果是0,则调用 addWorker(commond,true)添加一个线程(这个线程将去获取已经加入任务队列的本次任务并执行,个人认为就是线程池初始化的时候用的)
        • 如果不为0,进入步骤4;
      • 如果线程不Running状态,并且从任务队列中移除此任务成功,则拒绝本次任务;
    • 第四步: 将线程池扩容至maximumPoolSize并调用 addWorker(commond,false)方法创建普通线程执行任务,失败则拒绝本次任务。
  • 至此execute方法结束,我们再看其对应的流程图
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第9张图片

ThreadPoolThread源码解析之addWorker():

  • addWorker(Runnable firstTask, boolean core)方法的主要工作是在线程池中创建一个核心 / 普通线程并执行;
  • 参数:
    • firstTask参数: 新任务;
    • core参数:
      • 为true: 表示在新增核心线程时会判断当前活动线程数是否少于corePoolSize
      • 为false: 表示新增普通线程前需要判断当前活动线程数是否少于maximumPoolSize
  • 源码详读:
private boolean addWorker(Runnable firstTask, boolean core) {
     
    //外循环:                                                       外循环判断线程池状态
    retry:
    for (;;) {
     
        int c = ctl.get();
        // 获取线程池状态
        int rs = runStateOf(c);
    /*
    	if语句是用来判断什么状态的线程池不能接收任务
    	只有两种状态能接收任务:
    	1、线程池为非Running状态(Running状态则既可以新增核心线程处理新任务也可以处理同步队列的任务)
    	2、线程为shutdown状态且firstTask为空且队列不为空(可以新增空任务的线程来处理队列中的任务,但是不能接收新任务)
    	因此对应就写出了if判断
     */
        if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
            return false;
            //到此就能够说明满足了执行任务的条件
          //内循环:线程池添加核心线程并返回是否添加成功的结果
        for (;;) {
      												内循环判断创建线程条件➕增加工作线程
            // 获取线程数
            int wc = workerCountOf(c);
          	/* 
          		校验线程池已有线程数量是否超限:
           	    如果wc超过CAPACITY,也就是低29位的全为1,返回false;
           	    这里的core是addWorker方法的第二个参数:
           	    	如果为true表示根据corePoolSize来比较,创建核心线程;
                	如果为false则根据maximumPoolSize来比较,创建普通线程;
            */ 
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 尝试增加workerCount(通过CAS操作),如果成功,则跳出外层循环// 
            if (compareAndIncrementWorkerCount(c))
                break retry;
           /*
			CAS失败有两个原因:
				1、线程池状态变了,不能增加任务了
				2、工作线程数量变了
	    	*/
            // 如果增加workerCount失败,则我们判断是否是因为第一个原因造成的;重新获取线程池的同补状态及线程数
            c = ctl.get();  // Re-read ctl
            // 如果当前的运行状态不等于rs,说明状态已被改变,则重新进行外层循环判断线程池状态,否则内循环
            if (runStateOf(c) != rs)
                continue retry;
        }
    }
     /*
     * 工作线程数+1成功的后续操作:添加到工作线程集合,并启动工作线程,对应相应的标记记录
     * worker是ThreadPoolExecutor的内部类,包装任务线程等相关信息
     */
    boolean workerStarted = false;					将创建的工作线程放入到工作线程集合,也就是线程池中并执行
    boolean workerAdded = false;
    Worker w = null;
    try {
     
     // 根据firstTask来创建任务对象
        w = new Worker(firstTask);
     // 每一个任务对象都会创建一个线程
        final Thread t = w.thread;
        if (t != null) {
     
        	//加锁
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
     
            // 持锁期间重新检查线程池状态;
            //线程工厂创建线程失败或获取锁之前关闭的情况发生时,退出
                int rs = runStateOf(ctl.get());
                // 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且firstTask为null,向线程池中添加线程。
                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();
                    // largestPoolSize记录着线程池中出现过的最大线程数量,s是为其更新用的
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                        //线程添加成功
                    workerAdded = true;
                }
            } finally {
     
                mainLock.unlock();
            }
            if (workerAdded) {
       // 工作线程添加成功,则需要启动该线程
                // 启动线程
                t.start();     //这里其实执行的是包含线程的worker的run方法,也就是调用了runWorker(this)方法
                workerStarted = true;  //线程工作标识为true
            }
        }
    } finally {
     
     //线程启动失败,则进入addWorkerFailed
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
          
   //线程启动失败,则将工作线程去除,减少工作线程数量,尝试将线程池状态改为Terminate
    private void addWorkerFailed(Worker w) {
     
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
     
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            tryTerminate();
        } finally {
     
            mainLock.unlock();
        }
    }
  • 我们再简述一遍其流程:大致分为三个板块,外层循环、内层循环、核心线程操作;
    • 外层循环: 通过if语句判断线程池的状态是否可以新增工作线程,如下:
      • 线程池为非Running状态(Running状态则既可以新增核心线程处理新任务也可以处理同步队列的任务);
      • 线程为shutdown状态且firstTask为空且队列不为空(可以普通线程来处理队列中的任务,但是不能接收新任务);
    • 内层循环: 线程池添加核心线程并返回是否添加成功的结果:
      • 首先校验线程池已有线程数量是否超限,超了则返回false,否则进入下一步;
      • 通过CAS使工作线程数compareAndIncrementWorkerCount(c)+1,成功则进入下一步,失败则再次校验线程池是否是之前的状态,是则继续内层循环,不是则返回外层循环
    • CAS操作使核心线程数+1成功后的操作: 将线程添加到工作线程集合,并启动工作线程:
      • 判断线程是否为空,非空则获取锁,然后再次校验线程池状态,通过则进入下一步,未通过则添加线程workerAdded失败 ;
      • 线程池状态校验通过后,再检查线程是否已经启动,是则抛出异常,否则尝试将线程加入线程池;
      • 检查线程是否启动成功,成功则返回true,失败则进入 addWorkerFailed 方法;
  • 至此addWorker方法结束,我们再看其对应的流程图
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第10张图片

ThreadPoolThread内部类Worker源码解析:

  • WorkerThreadPoolExecutor的一个内部类,我们先看一下Worker的继承关系:
    private final class Worker extends AbstractQueuedSynchronizer implements Runnable
  • 通过其继承关系我们可以知道:
    • 它实现了Runnable接口,所以可以拿来当线程用;
    • 同时它还继承了AbstractQueuedSynchronizer同步器类,主要用来实现一个不可重入的锁,之所以不是重入锁,是因为我们不希望工作任务在调用setCorePoolSize之类的池控制方法时能够重新获取锁;
  • 另外,为了在线程真正开始运行任务之前禁止中断,我们将锁状态初始化为负值,并在启动时清除它(在runWorker中)。
  • 线程池中的每一个线程被封装成一个Worker对象,ThreadPool维护的其实就是一组Worker对象;
  • thread是在调用构造方法时通过ThreadFactory来创建的线程,是用来处理任务的线程;
  • Worker类主要维护正在运行任务的线程的中断控制状态,以及其他相关信息的记录,这个类继承了AbstractQueuedSynchronizer类,以简化获取和释放锁(该锁作用于每个任务执行代码)的过程。这样可以防止去中断正在运行中的任务,只会中断在等待从任务队列中获取任务的线程。
    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) {
     
        	//设置AQS的同步状态
        	// state:-1为初始值,0为unlock状态,1为lock状态
            setState(-1); //目的:在调用runWorker前,禁止中断
            this.firstTask = firstTask;
		/*
		创建一个Thread,通过线程工厂产生线程
		在调用构造方法时,需要把任务传入,这里通getThreadFactory().newThread(this)来新
		建一个线程,newThread方法传入的参数是this,因为Worker本身继承了Runnable接口,也
		就是一个线程,所以一个Worker对象在启动的时候会调用Worker类中的run方法。
		*/
            this.thread = getThreadFactory().newThread(this);
        }
        public void run() {
     
        //这里调用了ThreadPoolExecutor的runWorker方法,后面会详细介绍
            runWorker(this);
        }
        protected boolean isHeldExclusively() {
     
            return getState() != 0;
        }
    /*
     * 尝试获取同步状态的方法,重写AQS的tryAcquire(),
     */
        protected boolean tryAcquire(int unused) {
     
    //通过CAS设置同步状态,所以当初始值为-1时,更改不了同步状态,也就获取不了锁
    //即使可以获取锁,每次都是0->1,保证了锁的不可重入性
            if (compareAndSetState(0, 1)) {
     
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
       //尝试释放锁,不过不是将state设置为-1,而是置为0
        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(); }
	//如果state>=0、t!=null、且t没有被中断,则线程可以中断
	//同时可以说明:初始化时,state==-1,说明初始化时不能中断
   void interruptIfStarted() {
     
     Thread t;
     if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
     
            try {
     
                 t.interrupt();
              } catch (SecurityException ignore) {
     
              }
          }
      }
    }
  • 接下来我们进一步看runWorker()

ThreadPoolThread源码解析之runWorker():

  • runWorker(Worker w) 是线程池中真正处理任务的方法;
  • 参数:
    • Worker w: 封装的Worker,携带了工作线程的诸多要素,包括Runnable(待处理任务)、lock(锁)等
  • 我们先看源码再分析;
    final void runWorker(Worker w) {
     
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
   /*
   w.unlock()的原因:  
   new Worker()是state==-1,此处是调用Worker类的tryRelease()方法,将state置为0,
   而interruptIfStarted()中只有state>=0才允许调用中断,所以这里是为了能够响应中断   
   */
        w.unlock(); 
   // 线程退出的原因,true是任务导致,false是线程正常退出
        boolean completedAbruptly = true;
        try {
     
   //当任务队列为空且当前任务为空,停止循环
            while (task != null || (task = getTask()) != null) {
     
   /*上锁的两个作用:
   			1、可以防止在shutdown()时终止正在运行的worker: 因为shutdown()中设置中断需要先tryLock()
   			2、并发   
   */
                w.lock();
   /*
   根据状态判断中断:
 	1、如果线程池的状态>=stop,并且wt是未被中断的,则可以执行wt.interrupt(),否则跳过
 	2、注意:一开始判断线程池状态=STOP(以消除该瞬间shutdownNow()方法生效,使线程
 	池处于STOP或TERMINATED),
   */
                if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
                 &&!wt.isInterrupted())
                    wt.interrupt();
                try {
     
                //执行前(空方法,由子类重写实现)
                    beforeExecute(wt, task);
                    try {
     
                        task.run();   //Runnable类的
                        //执行后(空方法,由子类重写实现)
                        afterExecute(task, null);
                    } catch (Throwable ex) {
     
                    //执行后(空方法,由子类重写实现)
                        afterExecute(task, ex);
                        throw ex;
                    }
                } finally {
     
                    task = null;
                    //完成的任务数+1
                    w.completedTasks++; 
                    //释放锁
                    w.unlock();
                }
            }
            //到此,线程是正常退出
            completedAbruptly = false;
        } finally {
     
        	//处理worker的退出
            processWorkerExit(w, completedAbruptly);
        }
    }
  • 我们再简述一遍其流程:
    • 判断任务队列或者当前任务是否都为空:
      • 若都为空,则将completedAbruptly置为false,然后执行processWorkerExit(w,completedAbruptly)方法进入线程退出程序;
      • 若有其一不为空,则进入循环,并加锁;
    • 进入循环后,则判断是否需要设置中断标识,以下两个条件都满足则设置中断标识:
      • 如果一开始线程池的状态>=stop, 或者 一开始判断线程池状态Thread.interrupted()为true,即线程已经被中断,再次检查线程池状态是否>=STOP(以消除该瞬间shutdown方法生效,使线程池处于>=STOP状态);
      • wt未设置中断标识;
    • 执行前置方法 beforeExecute(wt, task)该方法为空方法,由子类实现)后执行task.run() 方法执行任务(执行不成功抛出相应异常);
    • 执行后置方法 afterExecute(task, thrown)该方法为空方法,由子类实现)后将线程池已完成的任务数+1,并释放锁,请勿清空;
    • 再次进行while循环;
  • 至此runWorker(Worker w)方法结束,我们再看其对应的流程图
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第11张图片

ThreadPoolThread源码解析之getTask():

  • 在上述runWorker(Worker w)中,我们看到了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.
    /*
     如果线程池为`Shutdown`状态且任务队列为空(线程池shutdown状态可以处理任务队列中的任务,不再接受新任务,这个是重点)
     或者 
     线程池状态>=STOP,则意味着线程池不必再获取任务了,且将当前工作线程数量-1并返回null,否则接着往下执行;
     */
        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;
 
    /*
     * 两个条件全部为true,则通过CAS使工作线程数-1,即去除工作线程:
     * 条件1:工作线程数大于maximumPoolSize,或(工作线程阻塞时间受限且上次在任务队列拉取任务超时)
     * 条件2:wc > 1或任务队列为空
     * 如果减1失败,则返回重试。
     */
        if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
     
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
        try {
     
    /*
    * 执行到这里,说明已经经过前面重重校验,开始真正获取task了;
    * 根据timed来判断,如果工作线程阻塞时间受限,则通过阻塞队列的poll方法进行超时控制,如果在keepAliveTime时间内没有获
    * 取到任务,则返回null;否则通过take方法;
    * 如果这时队列为空,则take方法会阻塞直到队列不为空。
    * 一般poll()用于普通线程、take()用于核心线程
    */
            Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
            if (r != null)   // r不为空,则返回该Runnable
                return r;
            // 如果 r == null,说明已经超时得不到任务,timedOut设置为true
            timedOut = true;
        } catch (InterruptedException retry) {
     
            // 如果获取任务时当前线程发生了中断,则设置timedOut为false并返回循环重试
            timedOut = false;
        }
    }
}
  • 这里重要的地方是第二个if判断:目的是控制线程池的有效线程数量;
  • 由源码分析可知,在执行execute方法时,如果当前线程池的线程数量超过了corePoolSize且小于maximumPoolSize,并且workQueue已满时,则可以增加工作线程(普通线程),但这时如果超时没有获取到任务,也就是timedOuttrue的情况,说明workQueue已经为空了,也就说明了当前线程池中不需要那么多线程来执行任务了,可以把多于corePoolSize数量的线程销毁掉,保持线程数量在corePoolSize即可;
  • 那普通线程什么时候会销毁?
    • 当然是runWorker方法执行完之后,也就是Worker中的run方法执行完,由JVM自动回收。
  • 注意: getTask方法返回null时,在runWorker方法中会跳出while循环,然后会执行processWorkerExit方法。
  • 我们再简述一遍其流程:
    • timedOut(获取任务是否超时)置为false(首次执行方法,无上次,自然为false),进入一个无限循环;
    • 如果线程池为Shutdown状态且任务队列为空(线程池shutdown状态可以处理任务队列中的任务,不再接受新任务,这个是重点)或者线程池状态>=STOP,则意味着线程池不必再获取任务了,当前工作线程数量-1并返回null,否则接着往下执行;
    • 接着if判断是否满足:线程池数量超限制或者时间超限且(任务队列为空或当前线程数>1):
      • 满足,则移除工作线程(普通线程),成功则返回null,不成功进入下一轮循环;
      • 不满足则继续往下执行;
    • 尝试用poll() 或者 take()(具体用哪个取决于timed的值)获取任务:
      • 如果任务不为空,则返回该任务;
      • 如果为空,则将timeOut置为 true进入下一轮循环。如果获取任务过程发生异常,则将 timeOut置为 false 后进入下一轮循环。
  • 至此getTask()方法结束,我们再看其对应的流程图
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第12张图片

ThreadPoolThread源码解析之processWorkerExit():

  • processWorkerExit(Worker w, boolean completedAbruptly)执行线程退出的方法;
  • 参数:
    • Worker w: 要结束的工作线程;
    • boolean completedAbruptly: 是否突然完成(异常导致);
  • 源码详读:
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
     
        /*  
     	 工作线程的两种操作:
     	 1、如果completedAbruptly值为true,则说明线程执行时出现了异常,需要将正在工作的线程(workerCount)数量-1;
    	 2、如果completedAbruptly值为false,也就是线程执行时没有出现异常,说明在getTask()方法中已经对workerCount进行了
    	 减1操作,这里就不必再减了。  
		*/
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();
		//从线程set集合中移除工作线程,该过程需要加锁
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
     
      	  //统计完成的任务数:将该worker已完成的任务数追加到线程池已完成的任务数
            completedTaskCount += w.completedTasks;
          //从HashSet中移除该worker
            workers.remove(w);
        } finally {
     
            mainLock.unlock();
        }
		//根据线程池状态进行判断是否结束线程池
        tryTerminate();
		/*
		当线程池是RUNNING或SHUTDOWN状态时:
			1、如果worker是异常结束,那么会直接addWorker;
			2、如果worker不是异常结束:
				2.1:如果allowCoreThreadTimeOut=true,并且等待队列有任务,至少保留一个worker来处理任务;
				2.2:如果allowCoreThreadTimeOut=false,workerCount不少于corePoolSize;
		*/
        int c = ctl.get();
        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);
        }
    }
  • 我们再简述一遍其流程:
  • 如果 completedAbruptly 为 true,即工作线程因为异常突然死亡,则执行工作线程-1操作;
  • 主线程获取锁后,线程池已经完成的任务数追加 w(当前工作线程) 完成的任务数,并从worker的set集合中移除当前worker;
  • 根据线程池状态进行判断是否执行tryTerminate()结束线程池;
  • 是否需要增加工作线程,如果线程池还没有完全终止,仍需要保持一定数量的线程;
  • 如果当前线程是突然终止的,调用addWorker()创建工作线程
  • 当前线程不是突然终止,但当前工作线程数量小于线程池需要维护的线程数量,则创建工作线程。需要维护的线程数量为corePoolSize(取决于成员变量 allowCoreThreadTimeOut是否为 false)或1。
  • 我们通过一个图对以上的方法进行一个总结:
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第13张图片

AbstractExecutorService源码解析之Callable任务带来的异常不输出问题:

  • ThreadPoolExecutor类中,任务提交方法的入口往往是是execute(Runnable command)方法,其实submit()方法本质也是调用了execute()(当然ThreadPoolExecutor不能执行这个方法,而是通过ForkJoinPool来实现的),只不过对任务进行了包装;
  • 不过从JDK5以后,工作单元和执行机制已经分离开来:
    • 工作单元:Runnable、Callable,它们之间的区别前面已经提过;
    • 执行机制:Executor框架提供;
  • 我们通过图看一下其结构:
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第14张图片
    public Future<?> submit(Runnable task) {
         //AbstractExecutorService的方法
        if (task == null) throw new NullPointerException();
        //newTaskFor(task) :将任务封装成一个FutureTask类。
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask); 
        return ftask;
    }
  • 通过源码可知,对一个有返回值的任务的实现:本质就是对任务做了一层封装,将其封装成RunnableFuture 对象,而最终它其实是一个 FutureTask 对象,在被添加到线程池的工作队列,然后调用 start() 方法后, FutureTask 对象的 run() 方法开始运行,即本任务开始执行:
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之线程池(最全,最深,最喜欢!!!!)_第15张图片
    public void run() {
     
        if (state != NEW || !RUNNER.compareAndSet(this, 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 = null;
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
  • FutureTask 对象的 run() 方法中,该任务抛出的异常被捕获,然后放在setException(ex)方法中;
  • 抛出的异常会被放到 outcome 对象中:
    protected void setException(Throwable t) {
     
        if (STATE.compareAndSet(this, NEW, COMPLETING)) {
     
            outcome = t;    //异常存放
            STATE.setRelease(this, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }
  • 这个对象outcome 就是submit()方法会返回的FutureTask对象执行get()方法得到的结果;
  • 但是在线程池中,并没有获取执行该结果,所以异常也就没有被抛出来,即被“吞掉”了,这就是线程池的 submit() 方法提交任务没有异常抛出的原因;
  • 不过我们也有对应的解决方法:
    • 在定义 ThreadFactory 的时候调用setUncaughtExceptionHandler方法,自定义异常处理方法。 例如:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("judge-pool-%d")
                .setUncaughtExceptionHandler((thread, throwable)-> logger.error("ThreadPool {} got exception", thread,throwable))
                .build();
  • 这样,对于线程池中每条线程抛出的异常都会打下 error 日志!!!

到底就结束了对线程池的相关学习,感谢各位大佬的博客借鉴。

你可能感兴趣的:(J.U.C,源码)