线程池复习

文章目录

  • 0. 基础知识
    • Java 线程模型
    • 创建线程的方式
    • 线程的生命周期
    • sleep,wait,park 区别
    • 中断对线程的影响
    • 守护线程
    • Java 内存模型(JMM)
  • 1. FutureTask
    • FutureTask,Future ,Runnable
    • task 的状态
    • 构造方法
    • run
    • get
    • cancel
  • 2. ThreadPoolExecutor
    • 体系结构
    • 线程池的构造方法
    • 线程池的生命周期
    • 线程池状态转换的方法
    • 方法调用链
    • execute
    • addWorker (mainLock)
    • Work
    • runWorker(w.lock())
    • getTask
    • processWorkerExit(mainLock)
    • tryTerminate(mainLock)
    • awaitTermination(mainLock)
    • shutdown/shutdownNow
    • 一些问题
    • 异常处理
    • 拒绝策略
  • 3. Executors 创建线程池
  • 4. ArrayBlockingQueue
    • 基本介绍
    • 入队
    • 出队
    • 彩蛋
  • 5. LinkedBlockingQueue
    • 基本介绍
    • put
    • take
    • 总结
  • 6. SynchronousQueue
    • 总结
    • 彩蛋

0. 基础知识

Java 线程模型

  • 参考文献

    死磕 java线程系列之线程模型

    基本都是参考 彤哥读源码 公众号进行学习,包括后面的一堆东西, 力荐

  • 线程的类型: 用户线程和内核线程

    在Java中,我们平时所说的并发编程、多线程、共享资源等概念都是与线程相关的,这里所说的线程实际上应该叫作“用户线程”,而对应到操作系统,还有另外一种线程叫作“内核线程”。

    用户线程位于内核之上,它的管理无需内核支持;而内核线程由操作系统来直接支持与管理。几乎所有的现代操作系统,包括 Windows、Linux、Mac OS X 和 Solaris,都支持内核线程。

    最终,用户线程和内核线程之间必然存在某种关系,建立这种关系常见的三种方法:多对一模型、一对一模型和多对多模型。

  • 线程模型

    多对一模型

    线程池复习_第1张图片

    一对一模型

    线程池复习_第2张图片

    多对多模型:

    线程池复习_第3张图片


创建线程的方式

  • 参考文献

    死磕 java线程系列之创建线程的8种方式

  • 继承Thread类并重写run()方法

    这种方式的弊端是一个类只能继承一个父类,如果这个类本身已经继承了其它类,就不能使用这种方式了。

  • 实现 Runnable 接口: 这种方式的好处是一个类可以实现多个接口,不影响其继承体系。

    public interface Runnable {
            
        public abstract void run();
    }
    
  • 匿名内部类

    // Thread匿名类,重写Thread的run()方法
    new Thread(){
            
        @Override
        public void run() {
            
            System.out.println(getName()+ "is Running");
        }
    }.start();
    
    // Runnable匿名类,实现其run()方法
    new Thread(new Runnable() {
            
        @Override
        public void run() {
            
            System.out.println(Thread.currentThread().getName()+ "is Running");
        }
    }).start();
    
    // 同上,使用lambda表达式函数式编程
    new Thread(()->{
            
        System.out.println(Thread.currentThread().getName()+ "is Running");
    }).start();
    
  • 实现 Callable 接口

    public interface Callable<V> {
            
        V call() throws Exception;
    }
    
  • 定时器 (java.util.Timer)

    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
            
        @Override
        public void run() {
            
            System.out.println(Thread.currentThread().getName()+ "is Running");
        }
    }, 0, 1000);
    
  • 线程池

    ExecutorService threadPool = Executors.newFixedThreadPool(5);
    threadPool.execute(()->{
             //Runnable
        System.out.println(Thread.currentThread().getName()+ "is Running");
    });
    
  • 本质上就两种,一种是继承Thread类并重写其run()方法,一种是实现Runnable接口的run()方法

  • 彩蛋之 时继承Thread并实现Runnable接口,应该输出什么呢?

    new Thread(()->{
            
    	System.out.println("Runnable: " + Thread.currentThread().getName());
    }){
            
      @Override
      public void run() {
            
      	System.out.println("Thread: "+ getName());
      }
    }.start();
    //Thread: Thread-0
    /*
      在 init 初始化时, 会将 target 进行赋值, 但是 run 方法被重写了,因此 runnable 不会生效,
      只输出 Thread 的 run 方法的内容
      @Override
      public void run() {
      	if (target != null) {
      		target.run();
          }
      }
    */
    

线程的生命周期

  • 参考文献:

    很遗憾,没有一篇文章能讲清楚线程的生命周期!牛逼

  • 线程的状态

    //BLOCKED状态只有线程处于synchronized的同步队列的时候才会有这个状态,其它任何情况都跟这个状态无关;
    // 这里感觉和下面注释说的不一样
    //Thread.java  
    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()方法后【本文由公从号“彤哥读源码”原创】_NB
         * 2. Object.wait(timeout)方法后且未到超时时间前,如果达到超时了或被notify()了会进入BLOCKED状态
         * 3. Thread.join(timeout)方法后
         * 4. LockSupport.parkNanos(nanos)方法后
         * 5. LockSupport.parkUntil(deadline)方法后
         */
        TIMED_WAITING,
     
    	/**
         * 终止状态,线程已经执行完毕
         */
        TERMINATED;
    }
    
  • 流程图

    • 参考 彤哥读源码,写的太6了,透彻

    线程池复习_第4张图片

    • BLOCKED状态只有线程处于synchronized的同步队列的时候才会有这个状态,其它任何情况都跟这个状态无关;

sleep,wait,park 区别

  • 彤哥读源码

  • Thread.sleep() 和 Object.wait() 的区别

    sleep vs wait

    (1)Thread.sleep()不会释放占有的锁,Object.wait()会释放占有的锁;

    (2)Thread.sleep()必须传入时间,Object.wait()可传可不传,不传表示一直阻塞下去;

    (3)Thread.sleep()到时间了会自动唤醒,然后继续执行;

    (4)Object.wait()不带时间的,需要另一个线程使用Object.notify()唤醒;

    (5)Object.wait()带时间的,假如没有被notify,到时间了会自动唤醒,这时又分好两种情况,一是立即获取到了锁,线程自然会继续执行;二是没有立即获取锁,线程进入同步队列等待获取锁;

    其实,他们俩最大的区别就是Thread.sleep()不会释放锁资源,Object.wait()会释放锁资源。

  • Thread.sleep() 和 Condition.await() 的区别

    Object.wait()和Condition.await()的原理就比较类似, 回答思路和上面差不多。

    Condition.await()底层是调用LockSupport.park()来实现阻塞当前线程的。

    Condition.await( ): 在阻塞当前线程之前还干了两件事,一是把当前线程添加到条件队列中,二是“完全”释放锁,也就是让state状态变量变为0,然后才是调用LockSupport.park()阻塞当前线程

  • Thread.sleep() 和 LockSupport.park() 的区别

    (1)从功能上来说,Thread.sleep()和LockSupport.park()方法类似,都是阻塞当前线程的执行,且都不会释放当前线程占有的锁资源

    (2)Thread.sleep()没法从外部唤醒,只能自己醒过来

    (3)LockSupport.park()方法可以被另一个线程调用LockSupport.unpark()方法唤醒

    (4)Thread.sleep()方法声明上抛出了InterruptedException中断异常,所以调用者需要捕获这个异常或者再抛出;

    (5)LockSupport.park()方法不需要捕获中断异常;

    (6)Thread.sleep()本身就是一个native方法;

    (7)LockSupport.park() 底层是调用的Unsafe的native方法;

  • Object.wait()和LockSupport.park()的区别

    (1)Object.wait()方法需要在synchronized块中执行;

    (2)LockSupport.park()可以在任意地方执行;

    (3)Object.wait()方法声明抛出了中断异常,调用者需要捕获或者再抛出;

    (4)LockSupport.park()不需要捕获中断异常;

    (5)Object.wait()不带超时的,需要另一个线程执行notify()来唤醒,但不一定继续执行后续内容;

    (6)LockSupport.park()不带超时的,需要另一个线程执行unpark()来唤醒,一定会继续执行后续内容;

    (7)如果在wait()之前执行了notify()会怎样?抛出IllegalMonitorStateException异常

    (8)如果在park()之前执行了unpark()会怎样?线程不会被阻塞,直接跳过park(),继续执行后续内容;

  • LockSupport.park()会释放锁资源吗?

    不会,它只负责阻塞当前线程,释放锁资源实际上是在Condition的await()方法中实现的。

  • 总结

    线程池复习_第5张图片

中断对线程的影响

  • interrupt( )、interrupted( ) 和isInterrupted( )

    interrupt( ) : 给当前线程设置一个停止的标记,并不会真正的停止线程

    interrupted( ): public static boolean interrupted() ,只能 通过 Thread 调用.检查主线程是否被设置了停止标识并清除中断状态

    isinterrupted( ): 检查调用该方法的线程是否被设置了停止标识

  • 示例代码

    //demo01
    public class ThreadInterrupt {
            
    
        public static void main(String[] args) {
            
            Thread t = new ThreadDemo01();
            t.start();
            t.interrupt(); //只是设置一个中断标志,线程是否停止取决于线程自己响不响应这个中断
            System.out.println(t.isInterrupted()); // true
            System.out.println(Thread.interrupted()); // false
        }
    }
    
    class ThreadDemo01 extends Thread{
            
        @Override
        public void run() {
            
            for (int i = 0; i < 100; i++) {
            
                System.out.println("i:"+i);
            }
        }
    }
    //demo02   interrupted() 会清除中断标志
    public static void main02(String[] args) {
            
        Thread.currentThread().interrupt(); 
        System.out.println(Thread.interrupted()); // true
        System.out.println(Thread.interrupted()); // false
    }
    //demo03
    public static void main03(String[] args) {
            
            Thread t = new ThreadDemo01();
            t.start();
            t.interrupt();
            System.out.println(t.isInterrupted()); // true
            System.out.println(t.isInterrupted()); // true
    }
    
    //demo04 ,响应中断
    public class ThreadInterrupt {
            
    
        public static void main(String[] args) throws InterruptedException {
            
            Thread t = new ThreadDemo02();
            t.start();
            Thread.sleep(1);
            System.out.println("====中断====");
            t.interrupt();
            /*
                ......
                ====中断====
                i:24
                java.lang.InterruptedException
                    at cn.yuan.test.threadpool.ThreadDemo02.run(ThreadInterrupt.java:58)
             */
        }
    }
    
    class ThreadDemo02 extends Thread{
            
        @Override
        public void run() {
            
            try{
            
                for (int i = 0; i < 1000; i++) {
            
                    //检测中断标志,并进行处理
                    if (this.isInterrupted()){
            
                        //使用 抛出异常来终止
                        throw new InterruptedException();
                    }
                    System.out.println("i:"+i);
                }
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
    }
    
  • interrupt()中断对LockSupport.park()的影响

    public static void main(String[] args) throws InterruptedException {
           
        Thread t= new Thread(()->{
           
            System.out.println("prepare park");
            LockSupport.park();
            System.out.println("restore from park");
        });
        t.start();
    
        Thread.sleep(3000);
        //唤醒的方式一
        //        LockSupport.unpark(t);
        System.out.println("unpark t");
        //唤醒的方式二
        t.interrupt();
    }
    

守护线程

  • Java中守护线程的总结

    在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

    用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:

    只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
    Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

    User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。

Java 内存模型(JMM)

死磕 java同步系列之JMM(Java Memory Model)

  • CPU 多级缓存 MESI 协议
  • 伪共享
  • JMM

线程池复习_第6张图片

总结:

(1)硬件内存架构使得我们必须建立内存模型来保证多线程环境下对共享内存访问的正确性;

(2)Java内存模型定义了保证多线程环境下共享变量一致性的规则;

(3)Java内存模型提供了工作内存与主内存交互的8大操作:lock、unlock、read、load、use、assign、store、write;

(4)Java内存模型对原子性、可见性、有序性提供了一些实现;

(5)先行发生的8大原则:程序次序原则、监视器锁定原则、volatile原则、线程启动原则、线程终止原则、线程中断原则、对象终结原则、传递性原则;

(6)先行发生不等于时间上的先发生;

1. FutureTask

FutureTask,Future ,Runnable

  • FutureTask 和 Future ,Runnable 之间的关系

    public class FutureTask<V> implements RunnableFuture<V>
    
    public class Thread implements Runnable
    
    // https://www.cnblogs.com/116970u/p/11509382.html 
    // 一个接口可以继承多个接口,但是不能实现一个接口
    public interface RunnableFuture<V> extends Runnable, Future<V>{
            
            void run();
    }
    
    public interface Runnable {
            
    	public abstract void run();
    }
    
    public interface Future<V> {
            
    
            boolean cancel(boolean mayInterruptIfRunning);
    
            boolean isCancelled();
    
            boolean isDone();
      
            V get() throws InterruptedException, ExecutionException;
    
            V get(long timeout, TimeUnit unit)
                throws InterruptedException, ExecutionException, TimeoutException;
    }
    
    public interface Callable<V> {
            
            V call() throws Exception;
    }
    

task 的状态

  • FutureTask task 的 7 种 状态

     //表示当前task状态
    private volatile int state;
    //当前任务尚未执行
    private static final int NEW          = 0;
    //当前任务正在结束,稍微完全结束,一种临界状态 completing
    private static final int COMPLETING   = 1;
    //当前任务正常结束
    private static final int NORMAL       = 2;
    //当前任务执行过程中发生了异常。 内部封装的 callable.run() 向上抛出异常了
    private static final int EXCEPTIONAL  = 3;
    //当前任务被取消
    private static final int CANCELLED    = 4;
    //当前任务中断中..
    private static final int INTERRUPTING = 5;
    //当前任务已中断
    private static final int INTERRUPTED  = 6;
    
    /*
    可能的状态转移:
        Possible state transitions:
        NEW -> COMPLETING -> NORMAL
        NEW -> COMPLETING -> EXCEPTIONAL
        NEW -> CANCELLED
        NEW -> INTERRUPTING -> INTERRUPTED
    */
    

构造方法

  • 构造方法和其他变量

    /** The underlying callable; nulled out after running */
    //submit(runnable/callable)   runnable 使用 装饰者模式 伪装成 Callable了。
    private Callable<V> callable;
    /** The result to return or exception to throw from get() */
    //正常情况下:任务正常执行结束,outcome保存执行结果。 callable 返回值。
    //非正常情况:callable向上抛出异常,outcome保存异常
    private Object outcome; // non-volatile, protected by state reads/writes
    /** The thread running the callable; CASed during run() */
    //当前任务被线程执行期间,保存当前执行任务的线程对象引用。
    private volatile Thread runner;
    /** Treiber stack of waiting threads */
    //因为会有很多线程去get当前任务的结果,所以 这里使用了一种数据结构 stack 头插 头取 的一个队列。
    private volatile WaitNode waiters;
    
    static final class WaitNode {
           
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() {
            thread = Thread.currentThread(); }
    }
    
    public FutureTask(Callable<V> callable) {
           
        if (callable == null)
            throw new NullPointerException();
        //就是程序员自己实现的业务类
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
    
    public FutureTask(Runnable runnable, V result) {
           
        //使用装饰者模式将runnable转换为了 callable接口,外部线程 通过get获取
        //当前任务执行结果时,结果可能为 null 也可能为 传进来的值。
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }
    //public class Executors
    public static <T> Callable<T> callable(Runnable task, T result) {
           
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }
    static final class RunnableAdapter<T> implements Callable<T> {
           
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
           
            this.task = task;
            this.result = result;
        }
        public T call() {
           
            task.run();
            return result;
        }
    }
    

run

  • run

    步骤:

    (1)如果当前 任务状态为 NEW, 且当前线程抢占 task 成功

    (2) 调用 Callable 的 call() 方法,并获取返回结果 result

    (3) 如果执行 call 发生异常,则 outcome 返回异常信息

    (4) 正常执行, outcome 返回 task 任务的结果

    (5) 最终都会调用 finishCompletion() 方法, 将通过 LockSupport.unpark(t); 唤醒 waitNode.

    finishCompletion 唤醒 waitNode 链表中的所有等待线程(即 get 阻塞的线程)

    WaitNode : 表示在调用 get() 方法是阻塞的线程构成的链表 (也算是队列吧头进头出)

    线程池复习_第7张图片

get

  • get

    步骤:

    (1) 判断task 执行的状态 state, 如果执行完成即 > COMPLETING,直接返回执行结果

    (2)如果还未完成 task, 执行 get 的线程外部线程会阻塞在 get()方法这里,进入 waitNode 链表进行排队

    (3) awaitDone: 线程获取不到 task 的结果时,排队等待 LockSupport.park(this),等待别人唤醒

    唤醒的几个地方(只在 finishCompletion 里面调用了 LockSupport.unpark(t)),即调用 finishCompletion 的地方

    • cancel 执行之后
    • 执行完 run (调用 call)之后,取到返回值 result, 设置到 outcome 后. set(result); setException(ex);

    线程池复习_第8张图片

cancel

  • cancel

    (1) task 的状态 state 为 NEW,才可以取消任务,

    (2)如果是发出中断信号来取消任务,会给线程发送中断信号 t.interrupt();

    (3)最后会执行 finishCompletion() , 唤醒所有的 get 操作阻塞的线程


2. ThreadPoolExecutor

  • 参考文献:

    彤哥读源码_死磕 java线程系列之线程池深入解析——体系结构

体系结构

  • 继承体系

    线程池复习_第9张图片

  • 彩蛋:

    定时任务线程池用的是哪种队列来实现的?

    • 延时队列。定时任务线程池中并没有直接使用并发集合中的DelayQueue,而是自己又实现了一个DelayedWorkQueue,不过跟DelayQueue的实现原理是一样的。

    延时队列使用什么数据结构来实现的呢?

    • 堆(DelayQueue中使用的是优先级队列,而优先级队列使用的堆;DelayedWorkQueue直接使用的堆)。
  • 知识图谱

    线程池复习_第10张图片

线程池的构造方法

构造方法

public ThreadPoolExecutor(int corePoolSize,//核心线程数限制
                          int maximumPoolSize,//最大线程限制
                          long keepAliveTime,//空闲线程存活时间
                          TimeUnit unit,//时间单位 seconds nano..
                          BlockingQueue<Runnable> workQueue,//任务队列
                          ThreadFactory threadFactory,//线程工厂
                          RejectedExecutionHandler handler/*拒绝策略*/)
//另外 4 个构造方法都是调用上面这个构造方法的.
  • keepAliveTime + unit: 线程保持空闲时间及单位

    默认情况下,此两参数仅当正在运行的线程数大于核心线程数时才有效,即只针对非核心线程。

    但是,如果allowCoreThreadTimeOut被设置成了true,针对核心线程也有效。

    即当任务队列为空时,线程保持多久才会销毁,内部主要是通过阻塞队列带超时的poll(timeout, unit)方法实现的

  • threadFactory: 线程工厂

    默认使用的是Executors工具类中的DefaultThreadFactory类,这个类有个缺点,创建的线程的名称是自动生成的,无法自定义以区分不同的线程池,且它们都是非守护线程。

    自定义线程工厂: 可以参考 com.google.common.util.concurrent.ThreadFactoryBuilder 中的实现方式

  • hadler

    常用的拒绝策略有丢弃当前任务、丢弃最老的任务、抛出异常、调用者自己处理等待。

    默认的拒绝策略是抛出异常,即线程池无法承载了,调用者再往里面添加任务会抛出异常。

属性

  • workers

    private final HashSet workers = new HashSet();

    线程池使用的是 HashSet 存放封装好的线程的

  • workQueue

    private final BlockingQueue workQueue;

    使用阻塞队列,来存放大于核心线程数后提交的任务, 线程调用 getTask 方法从队列中取 task 执行

线程池的生命周期

  • 参考文献

    死磕 java线程系列之线程池深入解析——生命周期

  • 源码

    //ThreadPoolExecutor.java
    public class ThreadPoolExecutor extends AbstractExecutorService {
           
        //新建线程池时,它的初始状态为RUNNING
    	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;
    }
    
  • 分析

    • ctl: 线程池的状态和工作线程的数量共同保存在控制变量ctl中, 高三位保存运行状态,低29位保存工作线程的数量,也就是说线程的数量最多只能有(2^29-1)个,也就是上面的CAPACITY;

    • 线程池状态

      RUNNING: 表示可接受新任务,且可执行队列中的任务; //新建线程池时,它的初始状态为RUNNING

      SHUTDOWN: 表示不接受新任务,但可执行队列中的任务

      STOP: 表示不接受新任务,且不再执行队列中的任务,且中断正在执行的任务;

      TIDYING: 所有任务已经中止,且工作线程数量为0,最后变迁到这个状态的线程将要执行terminated()钩子方法,只会有一个 线程执行这个方法;

      TERMINATED:中止状态,已经执行完terminated()钩子方法;

    • 线程池复习_第11张图片


线程池状态转换的方法

  • RUNNING: 创建线程池的时候就会初始化ctl,而ctl初始化为RUNNING状态

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    
  • SHUTDOWN: SHUTDOWN状态下,可以执行workQueue 里面的任务,此时不能够在接收新任务了即 execute -> addWork 创建线程失败

    public void shutdown() {
            
     final ReentrantLock mainLock = this.mainLock;
     //获取线程池全局锁
     mainLock.lock();
     try {
            
         checkShutdownAccess();
         //设置线程池状态为SHUTDOWN
         advanceRunState(SHUTDOWN);
         //中断线程池中所有空闲线程(未持有锁)
         interruptIdleWorkers();
         //空方法,子类可以扩展
         onShutdown(); // hook for ScheduledThreadPoolExecutor
     } finally {
            
         //释放线程池全局锁
         mainLock.unlock();
     }
     
     tryTerminate();
    }
    
    private void advanceRunState(int targetState) {
            
     //自旋
     for (;;) {
            
         int c = ctl.get();
         //当前状态 >= targetState,则直接跳出自旋,
         //当前状态 < targetState,则将 c 的状态改成 targetState,修改成功跳出自旋
         if (runStateAtLeast(c, targetState) ||
             ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
             break;
     }
    }
    
  • STOP: STOP 状态下只能执行完最后一次被 getTask() 获取的任务,此时不能够在接收新任务了即 execute -> addWork 添加失败

    //1) 执行shutdownNow()方法时,会把线程池状态修改为STOP状态,同时标记所有线程为中断状态。
    //2) 会把任务队列中的所有未处理的任务全部出队, 这里是和 shutdown 不一样的地方.
    public List<Runnable> shutdownNow() {
            
     //返回值引用
     List<Runnable> tasks;
     final ReentrantLock mainLock = this.mainLock;
     //获取线程池全局锁
     mainLock.lock();
     try {
            
         checkShutdownAccess();
         //设置线程池状态为STOP
         advanceRunState(STOP);
         //中断线程池中所有空闲线程(未持有锁)
         interruptWorkers();
         //导出未处理的task
         tasks = drainQueue();
     } finally {
            
         mainLock.unlock();
     }
    
     tryTerminate();
     //返回当前任务队列中 未处理的任务。
     return tasks;
    }
    

    • 至于线程是否响应中断其实是在队列的take()或poll()方法中响应的,最后会到 AQS 中,它们检测到线程中断了会抛出一个InterruptedException 异常,然后 getTask() 中捕获这个异常,并且在下一次的自旋时退出当前线程并减少工作线程的数量。

    • getTask()

      private Runnable getTask() {
                
          //表示当前线程获取任务是否超时 默认false, true表示已超时
          boolean timedOut = false; // Did the last poll() time out?
      
          //自旋
          for (;;) {
                
              int c = ctl.get();
              int rs = runStateOf(c);
              
              //rs >= stop,就不能获取任务了, 即返回 null, 
              //rs = shutdown, 如果 workQueue 为空,则也不能获取任务执行了,返回 null
              if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                
                  //使用CAS+死循环的方式让 ctl值 - 1
                  decrementWorkerCount();
                  return null;
              }
      
              //执行到这里,有几种情况?
              //1.线程池是 RUNNING 状态
              //2.线程池是 SHUTDOWN 状态 但是队列还未空
              
              //获取线程池中的线程数量
              int wc = workerCountOf(c);
      
              // Are workers subject to culling?
              //timed == true 表示当前这个线程 获取 task 时 是支持超时机制的,
              //使用queue.poll(xxx,xxx); 当获取task超时的情况下,下一次自旋就可能返回null了。
              //timed == false 表示当前这个线程 获取 task 时 是不支持超时机制的,当前线程会使用 queue.take();
              // allowCoreThreadTimeOut 为 true,表示核心线程数可以被回收, false: 核心线程数不可被回收
              //非核心线程 获取任务 超时, 会回收当前线程
              boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
              
              //大于最大线程数(可能情况是初始化后被修改了) 且 (线程数 > 1 或者 workQueue 为空) 时, 回收线程
              //当前线程可以被回收 且 获取任务超时 且 (线程数 > 1 或者 workQueue 为空) , 回收线程
              if ((wc > maximumPoolSize || (timed && timedOut))
                  && (wc > 1 || workQueue.isEmpty())) {
                
                  //使用CAS机制 将 ctl值 -1 ,减1成功的线程,返回null
                  //CAS成功的,返回Null
                  //CAS失败? 为什么会CAS失败?
                  //1.其它线程先你一步退出了
                  //2.线程池状态发生变化了。
                  if (compareAndDecrementWorkerCount(c))
                      return null;
                  //再次自旋时,timed有可能就是false了,因为当前线程cas失败,很有可能是因为其它线程成功退出导致的,再次咨询时
                  //检查发现,当前线程 就可能属于 不需要回收范围内了。
                  continue;
              }
      		
              try {
                
                  //获取任务的逻辑
                  Runnable r = timed ?
                      workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                  workQueue.take();
      
                  //条件成立:返回任务
                  if (r != null)
                      return r;
      
                  //说明当前线程超时了...
                  timedOut = true;
              } catch (InterruptedException retry) {
                
                  timedOut = false;
              }
          }
      }
      
  • TIDYING: 当执行shutdown()或shutdownNow()之后,会调用 tryTerminate( ) 方法.

    //如果所有任务已中止,且工作线程数量为0,就会设置线程池状态为 TIDYING
    final void tryTerminate() {
           
        //自旋
        for (;;) {
           
            int c = ctl.get();
            
            //1) Running 状态, 直接 return
            //2) >= TIDYING, 说明其他线程已经改变过状态了, 直接 return
            //3) = Shutdown 且 workQueue 不为空, 等待处理完队列任务在转换状态,直接 return.
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
    
            //什么情况会执行到这里?
            //1.线程池状态 = STOP
            //2.线程池状态为 SHUTDOWN 且 队列已经空了
    
            if (workerCountOf(c) != 0) {
            // Eligible to terminate
                //中断一个空闲线程。
                //空闲线程,在哪空闲呢? queue.take() | queue.poll()
                //1.唤醒后的线程 会在 getTask() 方法返回 null
                //2.执行退出逻辑的时候会再次调用 tryTerminate() 唤醒下一个空闲线程
                //3.因为线程池状态是 (线程池状态 >= STOP || 线程池状态为 SHUTDOWN 且 队列已经空了) 最终调用addWorker时,会失败。
                //最终空闲线程都会在这里退出,非空闲线程 当执行完当前task时,也会调用tryTerminate方法,有可能会走到这里。
                interruptIdleWorkers(ONLY_ONE);
                return;
            }
    
            //执行到这里的线程是谁?
            //workerCountOf(c) == 0 时,会来到这里。
            //最后一个退出的线程。 咱们知道,在 (线程池状态 >= STOP || 线程池状态为 SHUTDOWN 且 队列已经空了)
            //线程唤醒后,都会执行退出逻辑,退出过程中 会 先将 workerCount 计数 -1 => ctl -1。
            //调用 tryTerminate 方法之前,已经减过了,所以0时,表示这是最后一个退出的线程了。
    
            final ReentrantLock mainLock = this.mainLock;
            //获取线程池全局锁
            mainLock.lock();
            try {
           
                //设置线程池状态为 TIDYING 状态。
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
           
                    try {
           
                        //调用钩子方法
                        terminated();
                    } finally {
           
                        //设置线程池状态为TERMINATED状态。
                        ctl.set(ctlOf(TERMINATED, 0));
                        //唤醒调用 awaitTermination() 方法的线程。
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
           
                //释放线程池全局锁。
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }
    
  • TERMINATED: 调用 tryTerminate 设置完线程池状态为 TIDYING 之后, 随即将线程池状态修改为 TERMINATED

方法调用链

线程池复习_第12张图片

  • 提交任务通过 execute 方法
  • 是否创建线程通过调用 addWork() 方法
  • addWork() 创建成功后,启动线程后会执行 Work 中的 run,然后调用 runWorker() 执行任务
  • runWork 首先执行一开始提交任务而创建线程的那个任务, 然后通过 getTask() 从 workQueue 中去取 task
  • 执行 task 发生异常时(即 task 的异常未处理),会调用 processWorkerExit(work, true)
  • 剩下的就是一些关闭线程池的东西
    • shutdown
    • shutdownNow
    • tryTerminate

以下来源于 一枝花算不算浪漫公众号之并发编程

execute

public void execute(Runnable command) {
     
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
     
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
     
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

线程池复习_第13张图片

步骤:

1.当前线程数 通过 ctl 获取 小于 核心线程数, 此次提交任务直接创建一个新的 worker addWorker

2.添加成功,则直接返回 execute执行结束

3.添加失败 失败原因:其他线程先创建了 worker,此时线程数 > 核心线程数; 线程池状态发生了变化

  • 首先判断当前线程 是否为 Running 状态, 是 失败原因:其他线程创建了 worker,线程数 > 核心线程数 running 状态,则 将 task 放入 workQueue 传参进来的阻塞队列 中.

    • 添加成功, 再次判断线程池状态是否被改变,如果被改变了,则移除队列中的任务 task 未被消费才能 remove 成功, 并执行拒绝

      如果 移除失败,或者 线程池状态未发生改变,则 判断当前线程数是否为 0 如果核心线程数可以回收,则可能出现这种情况, 如果为 0,则创建一个空任务的 worker, 通过 getTask 获取 task 执行. execute 执行结束

    • 添加失败,则和 线程池不是 running 状态执行相同逻辑, 调用 addWorker 创建,小于最大线程数都能 创建 worker 成功.

      在失败,只能走拒绝策略了. execute 执行结束

  • 如果 不是 running 状态 失败原因是 线程池状态发生了变化 state > running ,则此时调用 addWorker 一定返回 false. 执行拒绝策略. execute 执行结束


addWorker (mainLock)

private boolean addWorker(Runnable firstTask, boolean core) {
     
    retry:
    for (;;) {
     
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
     
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
     
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
     
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
     
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
     
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
     
                mainLock.unlock();
            }
            if (workerAdded) {
     
                t.start();
                workerStarted = true;
            }
        }
    } finally {
     
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

线程池复习_第14张图片

步骤: (addWork 就是创建 worker, 创建成功启动 worker 线程执行 task) 只说一下添加成功/失败的情况

1.先判断是否可以创建 worker

  • 线程池状态是 running 可以提交任务

    • 线程数 < 核心线程数 / 最大线程数, 可以 创建 worker
  • 线程池状态是 shutdown task 为 null 且阻塞队列不为空,才允许创建 worker

    • 线程数 < 核心线程数 / 最大线程数, 可以 创建 worker
  • 线程池状态 >= stop addWorker 一定失败

2.可以创建 worker 后, 创建 worker 并启动线程 执行 task

  • 将 worker 加入线程池 workers HashSet 中需要==加锁==进行操作 保证同一时刻只有一个线程往线程池里 add

  • 添加成功, 则启动线程执行 Work 中的 run 方法 然后 调用 runWork() 方法

  • 添加失败, 失败的原因: 加锁之前线程池状态发生变化(shutdown 也要加锁,因此是加锁之前 线程池状态发生了变化) 则调用 addWorkerFailed(work) 方法, 将 worker 从 workers 中移除掉,然后将 ctl即线程数量 减一, 最后执行 tryTerminate() 尝试关闭线程池.


Work

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);
    }
}
//Thread.java
private Runnable target;
public void run() {
     
    if (target != null) {
     
        target.run();
    }
}
//thread.start() 执行的是 thread.run,因为 Work 实现了 Runnable 接口,重写了 run 方法
//即 开启线程后执行 runworker 方法

线程池复习_第15张图片

addWorker 成功之后,都会调用 t.start(); w = new Worker(firstTask); final Thread t = w.thread;, 启动线程执行 runWork 方法.


runWorker(w.lock())

final void runWorker(Worker w) {
     
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
     
        while (task != null || (task = getTask()) != null) {
     
            w.lock();
            if ( (  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 {
     
        //正常执行到这 completedAbruptly = false
        processWorkerExit(w, completedAbruptly);
    }
}

线程池复习_第16张图片

步骤:

  1. 给执行任务线程 worker 设置独占锁 因为 shutdown 方法会尝试获取 worker 的锁(w.tryLock() ),如果获取成功,则表示当前线程处于空闲状态,给当前线程一个中断信号, 所以线程执行任务的时候要加锁, 防止 shutdown 给当前线程中断信号

  2. 如果 task 不为空,则当前线程执行 task, task 为空则去 调用 getTask 去 workQueue 中获取任务

  3. 当 task 和 getTask 都为 null 时候退出循环

  4. 执行任务前后还有钩子方法 beforeExecute()/afterExecute(),可以自定义一些操作. afterExecute()可以接收到任务抛出的异常信息,方便我们做后续处理

  5. 最后会执行 processWorkerExit 方法, 此方法是用来清理线程池中添加的worker数据,completedAbruptly=true代表是异常情况下退出。


getTask

private Runnable getTask() {
     
    boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
     
        int c = ctl.get();
        int rs = runStateOf(c);
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
     
            decrementWorkerCount();
            return null;
        }
        
        int wc = workerCountOf(c);
        
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
     
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
     
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
     
            timedOut = false;
        }
    }
}

线程池复习_第17张图片

  • getTask方法用于从阻塞队列中获取任务,如果当前线程小于核心线程,那么当阻塞队列中没有任务时就会阻塞,反之会等待keepAliveTime后返回。

  • keepAliveTime的使用含义:非核心的空闲线程等待新任务的时间,当然如果这里设置了allowCoreThreadTimeOut=true也会回收核心线程。

  • allowCoreThreadTimeOut在构造ThreadLocalExecutor后设置的,默认为false。如果设置为true则代表核心线程数下的线程也是可以被回收的。

使用 poll 的情况:

  • 当前线程数 > 核心线程数
  • allowCoreThreadTimeOut 为 true即核心线程数可以回收 , 一直使用 poll 从队列中取 task

使用 take 的情况:

  • allowCoreThreadTimeOut 为 false 即核心线程数不给回收 且 当前线程数 < 核心线程数

processWorkerExit(mainLock)

private void processWorkerExit(Worker w, boolean completedAbruptly) {
     
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
     
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
     
        mainLock.unlock();
    }

    tryTerminate();

    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);
    }
}

线程池复习_第18张图片

执行processWorkerExit()代表在runWorker()线程跳出了当前循环,一般有两种情况:

  • task.run()内部抛出异常,直接结束循环,然后执行processWorkerExit(), completedAbruptly = true
  • getTask()返回为空,代表线程数量大于核心数量且workQueue中没有任务,此时需要执行processWorkerExit()来清理多余的Worker对象 completedAbruptly = false

步骤:

  1. 如果是 task.run 异常退出,则 将 ctl 值 减一 线程数减一
  2. 加锁, 将 worker 从当前线程池 works 中移除,并累加执行任务数量
  3. 调用 tryTerminate 尝试关闭线程池
  4. 判断当前线程池状态 为 running / shutdown 状态时,
    • 如果当前线程是异常退出 completedAbruptly = true, 则 addWork(null, false) , 重新创建一个 worker 顶替原来的线程
    • 如果当前线程是正常退出 completedAbruptly = false
      • allowCoreThreadTimeOut = true 时, workQueue 队列不为空, 至少要有一个 线程, 否则 addWorker(null, false);
      • allowCoreThreadTimeOut = false 时, 线程池中线程数量要 >= 核心线程数,小于则 addWorker(null, false);

tryTerminate(mainLock)

final void tryTerminate() {
     
    for (;;) {
     
        int c = ctl.get();
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) {
      // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
     
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
     
                try {
     
                    terminated();
                } finally {
     
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
     
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

线程池复习_第19张图片

步骤:

  1. 直接 return 的情况

    • 当前线程池的状态是 running. (此时线程池很正常, 直接返回)
    • 当前线程池的状态 >= TIDYING (说明,已经有其他线程执行了 TIDYING => TERMINATED )
    • 当前线程池状态 = shutdown, 且队列不为空, 直接返回,等待队列中的任务处理完,在转换状态
  2. 执行到这里, 线程池状态为 shutdown且 队列为空 或者 stop 状态 这两种情况.

  3. 线程池线程数量 > 0, 中断一个线程 然后 return. 这里中断是为了唤醒 park 的空闲线程,如果空闲线程获取不到任务,则return null,执行退出逻辑,又走到 tryTerminate 方法, 又会唤醒一个空闲线程

    • 为什么中断一个线程? 这里的设计思想是,如果线程数量特别多的话,只有一个线程去做唤醒空闲 worker 的任务可能会比较吃力,所以,就给了每个 被唤醒的 worker 线程 ,在真正退出之前协助 唤醒一个空闲线程的任务,提供吞吐量的一种常用手段。简单说就是: 一个线程唤醒了一个空闲线程, 当空闲线程退出时,在唤醒另一个空闲线程. 直到线程池线程数为 0, 然后转变线程池状态.
    • park 线程的地方 getTask 时调用 take 方法 且获取不到任务就会 park 阻塞在那, 如果被唤醒则会执行退出逻辑, 调用 processWorkerExit(), 然后在调用 tryTerminate 方法 尝试关闭线程池.此时线程池中线程数不为0, 则中断一个线程(唤醒一个空闲线程).
    • interrupt 能够唤醒一个 LockSupport.park 即 getTask 时 take 阻塞的线程
  4. 当上面中断唤醒了所有空闲线程并退出 即线程池线程数量 等于 0 时, 这是最后一个退出线程 获取全局锁将线程池状态修改为 TIDYING 状态, 并调用 钩子方法 terminated(). 线程池中止的时候会调用此方法。

  5. 最后设置线程池状态为TERMINATED状态,唤醒调用awaitTermination()方法的线程。


awaitTermination(mainLock)

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
     
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
     
        for (;;) {
     
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            if (nanos <= 0)
                return false;
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
     
        mainLock.unlock();
    }
}

该方法是判断线程池状态是否达到TERMINATED,如果达到了则直接返回true,没有达到则会await挂起当前线程指定的时间。


shutdown/shutdownNow

public void shutdown() {
     
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
     
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers(); //给所有空闲线程设置中断标志位
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
     
        mainLock.unlock();
    }
    tryTerminate();
}

//============================================================

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;
}

两个方法不一样的地方:

  1. shutdown 将状态转换为 shutdown, shutdownNow 将状态转换为 stop
  2. shutdown 调用 interruptIdleWorkers() ,然后给空闲线程 未加锁线程 设置中断标识,
    • 空闲的线程来源于: getTask take 获取不到任务会释放锁, 以及 pool 获取不到任务,释放锁, 为空闲状态.
    • 执行interrupt()后处于queue阻塞的线程,会被唤醒,唤醒后,进入下一次自旋判断线程池状态是否改变, 如果改变可能直接返回空, 然后回到 runWorker 退出循环,当前线程执行退出逻辑 processWorkerExit. 剩下的看上面
  3. shutdownNow 调用 interruptWorkers(), 给所有线程设置中断标志, 不仅仅是空闲线程.

对比:

  1. shutdown: 回收空闲线程,等待 workQueue 中的任务执行完毕, 然后关闭线程池
    • 将线程池状态变为SHUTDOWN,此时新任务不能被提交
    • workQueue中还存有的任务可以继续执行
    • 会像线程池中空闲的状态发出中断信号
  2. shutdownNow: 给所有线程设置中断标识, 然后从 workQueue 中取出未执行的任务,不等待 队列中的任务执行完毕, 只让线程正在执行的任务执行结束 就关闭线程池.
    • 将线程池的状态设置为STOP,此时新任务不能被提交
    • 线程池中所有线程都会收到中断的信号
    • ??????????线程处于wait状态,那么中断状态会被清除,同时抛出InterruptedException。 ????????

一些问题

  1. ThreadPoolExecutor核心线程池中线程预热功能?

    //开启所有核心线程
    public int prestartAllCoreThreads() {
           
        int n = 0;
        while (addWorker(null, true))
            ++n;
        return n;
    }
    
  2. ThreadPoolExecutor中创建的线程如何被复用的?

    • 从 runWorker()和getTask() 方法看
  3. ThreadPoolExecutor中关闭线程池的方法shutdownshutdownNow的区别?

    • 看上面
  4. ThreadPoolExecutor中存在的一些扩展点?

    • beforeExecute()/afterExecute():runWorker()中线程执行前和执行后会调用的钩子方法
    • terminated:线程池的状态从TIDYING状态流转为TERMINATED状态时terminated方法会被调用的钩子方法。
    • onShutdown:当我们执行shutdown()方法时预留的钩子方法。
  5. ThreadPoolExecutor支持动态调整核心线程数、最大线程数、队列长度等一些列参数吗?怎么操作?

    • 美团技术团队的一篇文章,写了如何动态调整: [Java线程池实现原理及其在美团业务中的实践]
    • setCorePoolSize():动态调整线程池核心线程数
    • setMaximumPoolSize():动态调整线程池最大线程数
    • setKeepAliveTime(): 空闲线程存活时间,如果设置了allowsCoreThreadTimeOut=true,核心线程也会被回收,默认只回收非核心线程
  6. Worker继承自AQS有何意义?

    • worker 只在 runWorker 中使用了, tryLock 在 interruptIdleWorkers 中使用
    • interruptIdleWorkers() 方法的意思是中断空闲线程的意思,它只会中断BlockingQueue的poll()或take()方法,而不会中断正在执行的任务。 如何区分空闲线程, 通过能不能获取锁来看, 这里没有使用ReentrantLock 的重入锁,而是直接 继承AQS
    • Worker 继承自 AQS 实际是要使用其锁的能力,这个锁主要是用来控制 shutdown() 时不要中断正在执行任务的线程。

面试官:线程池如何按照core、max、queue的执行循序去执行?(内附详细解析)

  1. 线程池如何按照core、max、queue的执行循序去执行?

    • 上面的应用场景: 一个线程池执行的任务属于IO密集型,CPU大多属于闲置状态,系统资源未充分利用。如果一瞬间来了大量请求,如果线程池数量大于coreSize时,多余的请求都会放入到等待队列中。等待着corePool中的线程执行完成后再来执行等待队列中的任务。 如何解决上面的问题? => 线程池按照 core, max, queue 的顺序执行

    • 解决思路: 在 线程数量 > 核心线程数 时, workQueue.offer(command) 失败, 直接 调用 addWorker(command, false) 创建 worker

      线程池复习_第20张图片

    • Dubbo 中 EagerThreadPool 的解决方案: 自定义一个 BlockingQueue.

      //TaskQueue.java
      public class TaskQueue<R extends Runnable> extends LinkedBlockingQueue<Runnable> {
                
       
          //...
          private EagerThreadPoolExecutor executor;
      
          public TaskQueue(int capacity) {
                
              super(capacity);
          }
          
          public void setExecutor(EagerThreadPoolExecutor exec) {
                
              executor = exec;
          }
          
          @Override
          public boolean offer(Runnable runnable) {
                
              if (executor == null) {
                
                  throw new RejectedExecutionException("The task queue does not have executor!");
              }
              int currentPoolThreadSize = executor.getPoolSize();
              // have free worker. put task into queue to let the worker deal with task.
              if (executor.getSubmittedTaskCount() < currentPoolThreadSize) {
                
                  return super.offer(runnable);
              }
              // return false to let executor create new worker.
              if (currentPoolThreadSize < executor.getMaximumPoolSize()) {
                
                  return false;
              }
      
              // currentPoolThreadSize >= max
              return super.offer(runnable);
          }
          
          //...
      }
      
  2. 线程池发生了异常该怎样处理?

    • 不管是使用execute()还是submit()提交任务,最终都会执行到ThreadPoolExecutor.runWorker() ,这里出现异常会直接向上抛出
    • 如果我们使用submit()来提交任务,在ThreadPoolExecutor.runWorker()方法执行时最终会调用到FutureTask.run()方法里面去, 如果业务代码抛出异常后,会被catch捕获到,然后调用setExeception()方法.

对于线程池、包括线程的异常处理推荐以下方式:

  • 直接使用try/catch,这个也是最推荐的方式
  • 在我们构造线程池的时候,重写uncaughtException()方法
  • 直接重写afterExecute()方法,感知异常细节

异常处理

  • ThreadPoolExcutor 线程池 异常处理 (上篇)

  • https://mp.weixin.qq.com/s/dqOy2eeeOsDa1AN3nNUftg

    执行 task 时候, 抛出异常后,如何处理?

    线程池中的线程出现了异常该怎样捕获?会导致什么样的问题?

    执行任务出现异常, runWork 里面执行 task.run 出问题, 然后=> processWorkerExit 线程数减1,然后将当前线程从线程池中移除,

    之后重新创建一个线程 addWork(null, false). 重新创建的这个线程启动 调用 runWork, 从 workQueue 中获取任务去执行.如果获取不到任务,可能会一直 阻塞 take / poll 两种方式获取任务

    在单个线程的线程池中一但抛出了未被捕获的异常时,线程池会回收当前的线程并创建一个新的 Worker; 它也会一直不断的从队列里获取任务来执行,但由于这是一个消费线程,根本没有生产者往里边丢任务,所以它会一直 waiting 在从队列里获取任务处,所以也就造成了线上的队列没有消费,业务线程池没有执行的问题。

  • 关闭线程池

    long start = System.currentTimeMillis();
    for (int i = 0; i <= 5; i++) {
            
        pool.execute(new Job());
    }
    
    pool.shutdown();
    //awaitTermination: 每隔一段时间,检查线程池状态是不是 terminated
    while (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
            
        LOGGER.info("线程还在执行。。。");
    }
    long end = System.currentTimeMillis();
    LOGGER.info("一共处理了【{}】", (end - start));
    

拒绝策略

  • public static class AbortPolicy implements RejectedExecutionHandler (默认拒绝策略)

    丢弃任务并抛出RejectedExecutionException异常

  • public static class DiscardPolicy implements RejectedExecutionHandler

    丢弃任务,但是不抛出异常

  • public static class DiscardOldestPolicy implements RejectedExecutionHandler

    丢弃队列最前面的任务,然后重新提交被拒绝的任务

  • public static class CallerRunsPolicy implements RejectedExecutionHandler

    由调用线程(提交任务的线程)处理该任务

3. Executors 创建线程池

  • https://www.cnblogs.com/zhujiabin/p/5404771.html
//ThreadPoolExecutor.java
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 
//======================================================================================

//newFixedThreadPool <= 核心线程数=最大线程数,没有超时时间,无界队列,默认的线程工厂和拒绝策略
//创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
//线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
public static ExecutorService newFixedThreadPool(int nThreads) {
     
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
//======================================================================================

//newCachedThreadPool <= 核心线程数 = 0, 最大线程数为 max, 等待超时时间 60s, 同步队列, 默认的线程工厂和拒绝策略
//创建一个可缓存的线程池。
//如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,
//当任务数增加时,此线程池又可以智能的添加新线程来处理任务。
//此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
public static ExecutorService newCachedThreadPool() {
     
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());//SynchronousQueue内部并没有数据缓存空间
}
//======================================================================================

//newScheduledThreadPool <= 核心线程数自定义, 最大线程数为 max, 无等待时间, 使用延迟队列.
//创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
     
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
     
    //public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory);
}
//======================================================================================

//newSingleThreadExecutor <= 核心线程数为 1,最大线程数为1, 无超时时间, 无界队列
//创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。
//如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。
//此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
public static ExecutorService newSingleThreadExecutor() {
     
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

总结:

  • newFixedThreadPool, newSingleThreadExecutor 阻塞队列都是无界队列 LinkedBlockingQueue, 可能会堆积大量的请求,从而导致 OOM
  • CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
  • 所以阿里巴巴也建议我们要自定义线程池核心线程数以及阻塞队列的长度

4. ArrayBlockingQueue

死磕 java集合之ArrayBlockingQueue源码分析

基本介绍

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
     
    
	// 使用数组存储元素
    final Object[] items;
   // 取元素的指针
    int takeIndex;
    // 放元素的指针
    int putIndex;
    // 元素数量
    int count;
    // 保证并发访问的锁
    final ReentrantLock lock;
    // 非空条件
    private final Condition notEmpty;
    // 非满条件
    private final Condition notFull;
	
    public ArrayBlockingQueue(int capacity) {
     
        this(capacity, false);
    }
    public ArrayBlockingQueue(int capacity, boolean fair) {
     
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
}

通过属性我们可以得出以下几个重要信息:

(1)利用数组存储元素;

(2)通过放指针和取指针来标记下一次操作的位置;

(3)利用重入锁来保证并发安全;

通过构造方法我们可以得出以下两个结论:

(1)ArrayBlockingQueue初始化时必须传入容量,也就是数组的大小;

(2)可以通过构造方法控制重入锁的类型是公平锁还是非公平锁;

入队

offer - pool, put - take.

(1)add(e)时如果队列满了则抛出异常;

(2)offer(e)时如果队列满了则返回false;

(3)put(e)时如果队列满了则使用notFull等待;

(4)offer(e, timeout, unit)时如果队列满了则等待一段时间后如果队列依然满就返回false;

(5)利用放指针循环使用数组来存储元素;

put 方法

public void put(E e) throws InterruptedException {
     
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
     
        // 如果数组满了,使用 notFull 等待
        // notFull 等待的意思是说现在队列满了
        // 只有取走一个元素后,队列才不满
        // 然后唤醒 notFull,然后继续现在的逻辑
        // 这里之所以使用 while 而不是 if (虚假唤醒???)
        // 是因为有可能多个线程阻塞在 lock 上
        // 即使唤醒了可能其它线程先一步修改了队列又变成满的了    
        // 这时候需要再次等待
        while (count == items.length)
            notFull.await();
        enqueue(e);//notEmpty.signal();
    } finally {
     
        lock.unlock();
    }
}

出队

(1)remove()时如果队列为空则抛出异常;

(2)poll()时如果队列为空则返回null;

(3)take()时如果队列为空则阻塞等待在条件notEmpty上;

(4)poll(timeout, unit)时如果队列为空则阻塞等待一段时间后如果还为空就返回null;

(5)利用取指针循环从数组中取元素;

take 方法

public E take() throws InterruptedException {
     
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        
		// 如果队列无元素,则阻塞等待在条件notEmpty上
        while (count == 0)
            notEmpty.await();
        return dequeue(); //notFull.signal();
    } finally {
     
        lock.unlock();
    }
}

彩蛋

线程池复习_第21张图片

5. LinkedBlockingQueue

死磕 java集合之LinkedBlockingQueue源码分析

基本介绍

LinkedBlockingQueue是java并发包下一个以单链表实现的阻塞队列,它是线程安全的

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
     
 	// 容量
    private final int capacity;
    // 元素数量
    private final AtomicInteger count = new AtomicInteger();
    // 链表头
    transient Node<E> head;
    // 链表尾
    private transient Node<E> last;
    // take锁
    private final ReentrantLock takeLock = new ReentrantLock();
    // notEmpty条件
	// 当队列无元素时,take锁会阻塞在notEmpty条件上,等待其它线程唤醒
    private final Condition notEmpty = takeLock.newCondition();
    // 放锁
    private final ReentrantLock putLock = new ReentrantLock();
    // notFull条件
	// 当队列满了时,put锁会会阻塞在notFull上,等待其它线程唤醒
    private final Condition notFull = putLock.newCondition();
    
    static class Node<E> {
     
        E item;
        Node<E> next;
        Node(E x) {
      item = x; }
    }
    
    public LinkedBlockingQueue() {
     
        // 如果没传容量,就使用最大int值初始化其容量
        this(Integer.MAX_VALUE);
    }
    public LinkedBlockingQueue(int capacity) {
     
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }
}

通过属性可以看出:

(1)capacity,有容量,可以理解为LinkedBlockingQueue是有界队列

(2)head, last,链表头、链表尾指针

(3)takeLock,notEmpty,take锁及其对应的条件

(4)putLock, notFull,put锁及其对应的条件

(5)入队、出队使用两个不同的锁控制,锁分离,提高效率

put

public void put(E e) throws InterruptedException {
     
    if (e == null) throw new NullPointerException();
    // Note: convention in all put/take/etc is to preset local var
    // holding count negative to indicate failure unless set.
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
	// 使用put锁加锁
    putLock.lockInterruptibly();
    try {
     
        // 如果队列满了,就阻塞在notFull条件上        
        // 等待被其它线程唤醒
        while (count.get() == capacity) {
     
            notFull.await();
        }
        // 队列不满了,就入队
        enqueue(node); // 直接加入单链表尾部,没有其他操作
		// 队列长度加1
        c = count.getAndIncrement();
        // 如果现队列长度如果小于容量
        // 就再唤醒一个阻塞在notFull条件上的线程
        // 这里为啥要唤醒一下呢?
        // 因为可能有很多线程阻塞在notFull这个条件上的
        // 而取元素时只有取之前队列是满的才会唤醒notFull
        // 为什么队列满的才唤醒notFull呢?
        // 因为唤醒是需要加putLock的,这是为了减少锁的次数
        // 所以,这里索性在放完元素就检测一下,未满就唤醒其它notFull上的线程
        // 说白了,这也是锁分离带来的代价
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
     
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

private void signalNotEmpty() {
     
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
     
        notEmpty.signal();
    } finally {
     
        takeLock.unlock();
    }
}

(1)使用putLock加锁;

(2)如果队列满了就阻塞在notFull条件上;

(3)否则就入队;

(4)如果入队后元素数量小于容量,唤醒其它阻塞在notFull条件上的线程;

(5)释放锁;

(6)如果放元素之前队列长度为0,就唤醒notEmpty条件;

take

public E take() throws InterruptedException {
     
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    // 使用takeLock加锁
    takeLock.lockInterruptibly();
    try {
     
        // 如果队列无元素,则阻塞在notEmpty条件上
        while (count.get() == 0) {
     
            notEmpty.await();
        }
        x = dequeue(); //单纯的出队操作,没有其他东西
        c = count.getAndDecrement();
        // 如果取之前队列长度大于1,则唤醒notEmpty
        if (c > 1)
            notEmpty.signal();
    } finally {
     
        takeLock.unlock();
    }
    // 如果取之前队列长度等于容量
	// 则唤醒notFull
    if (c == capacity)
        signalNotFull();
    return x;
}

private void signalNotFull() {
     
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
     
        notFull.signal();
    } finally {
     
        putLock.unlock();
    }
}

(1)使用takeLock加锁;

(2)如果队列空了就阻塞在notEmpty条件上;

(3)否则就出队;

(4)如果出队前元素数量大于1,唤醒其它阻塞在notEmpty条件上的线程;

(5)释放锁;

(6)如果取元素之前队列长度等于容量,就唤醒notFull条件;

总结

(1)LinkedBlockingQueue采用单链表的形式实现;

(2)LinkedBlockingQueue采用两把锁的锁分离技术实现入队出队互不阻塞;

(3)LinkedBlockingQueue是有界队列,不传入容量时默认为最大int值;

LinkedBlockingQueue与ArrayBlockingQueue对比?

a)后者入队出队采用一把锁,导致入队出队相互阻塞,效率低下;

b)前者入队出队采用两把锁入队出队互不干扰,效率较高;

c)二者都是有界队列,如果长度相等且出队速度跟不上入队速度,都会导致大量线程阻塞;

d)前者如果初始化不传入初始容量,则使用最大int值,如果出队速度跟不上入队速度,会导致队列特别长,占用大量内存;


6. SynchronousQueue

死磕 java集合之SynchronousQueue源码分析

总结

(1)SynchronousQueue是java里的无缓冲队列,用于在两个线程之间直接移交元素

(2)SynchronousQueue有两种实现方式,一种是公平(队列)方式,一种是非公平(栈)方式

(3)方式中的节点有三种模式:生产者、消费者、正在匹配中

(4)栈方式的大致思路是如果栈顶元素跟自己一样的模式就入栈并等待被匹配,否则就匹配,匹配到了就返回;

(5)队列方式的大致思路是……不告诉你^^(两者的逻辑差别还是挺大的)

  • https://developer.aliyun.com/article/713621

线程池复习_第22张图片

彩蛋

(1)SynchronousQueue真的是无缓冲的队列吗?

通过源码分析,我们可以发现其实 SynchronousQueue 内部或者使用栈或者使用队列来存储包含线程和元素值的节点,如果同一个模式的节点过多的话,它们都会存储进来,且都会阻塞着,所以,严格上来说,SynchronousQueue并不能算是一个无缓冲队列。

(2)SynchronousQueue有什么缺点呢?

试想一下,如果有多个生产者,但只有一个消费者,如果消费者处理不过来,是不是生产者都会阻塞起来?反之亦然。

这是一件很危险的事,所以,SynchronousQueue一般用于生产、消费的速度大致相当的情况,这样才不会导致系统中过多的线程处于阻塞状态。

你可能感兴趣的:(Java并发)