JUC-ThreadPoolExecutor原理

365 阅读5分钟

线程池解决了什么问题?

线程池其实主要解决的就是两个问题:

  1. 执行大量异步任务是,线程池提供了良好的性能,不是用线程池执行异步任务,每次都需要new Thread,而创建销毁线程其实是需要很大的系统开销的,而线程池的线程创建之后可以重复利用。
  2. 线程池提供了资源限制和管理的手段,可以限制线程个数,动态增加线程。同时有一些基本的统计功能,可以统计当前线程池完成的任务个数。

线程池的构造

线程池中,包含了一些重要的成员变量。

  • AtomicInteger ctl:这个成员变量,巧妙的利用了高低位来表示不同的含义,高三位,用来表示当前线程的运行状态,通过运行状态,线程池可以来限定任务的加入和执行机制。其他位可以用来表示线程池的线程个数。使用原子类,来保证更改的线程安全,以及可见性。
  • HashSet workers:用来存放工作线程,Worker类里其实存放了任务以及执行任务的Thread实例。
  • BlockingQueue workQueue:存放任务的阻塞队列,可以指定实现方式,比如ArrayBlokcingQueue等。
  • ReentrantLock mainLock:独占锁,用来保证workers HashSet的线程安全,插入删除操作,或者其他需要保证全局唯一的操作,如tryterminate操作。
  • Condition termination:mainlock的条件变量,当有线程调用awaitterminated操作时,会阻塞在这个条件变量的队列中,线程池shutdown时,会signalall唤醒阻塞的线程,阻塞的线程会恢复过来就可以进行其他的一些操作。

线程池的状态:

  • Running:接受新的任务并处理阻塞队列里的任务;
  • ShutDown:拒绝新的任务但是处理阻塞队列里的任务;
  • Stop:拒绝新的任务并且抛弃阻塞队列里的任务,并且会中断正在处理中的任务
  • Tidying(闲置):所有任务都执行完(包括阻塞队列里面的任务)后,当前线程池里的活动线程为0,这个时候会先CAS将状态设置为Tidying,然后将调用terminated()方法
  • Terminated:终止状态,terminated()方法调用完成之后,将会CAS设置线程池状态为终止。

线程池状态的转化:

  • Running->ShutDown:显示调用了shutdown()方法,或者调用了finalize()里面的shutdown()方法,所以都是调用了shutdown();
  • Running或ShutDown->Stop:显示的调用shutdownnow()方法的时候;
  • ShutDown->Tidying:调用shutDown()方法中,当所有任务都被处理或者中断之后,会将状态变为Tidying。然后会调用可以被继承的terminated()方法。
  • Tiding->Terminated:当shutdown()方法中调用完成terminated方法之后,会CAS设置为Terminated状态;

线程池的类型

可以用Executors类的静态方法创建线程池,但是其实里面也是调用了ThreadPoolExecutor类的构造方法,创建不同的线程池。

  • newFixedThreadPool:创建一个核心线程数和最大线程数都为nThreads传入参数的线程池,阻塞队列长度为Integer.MAX_VALUE.

  • newSingleThreadExecutor:创建一个核心线程和最大线程都为1的线程池,阻塞队列长度为Integer.MAX_VALUE。

  • newCachedThreadPool:创建一个按需创建的线程池,初始线程为0个,最多的线程数为Integer.MAX_VALUE。阻塞队列为同步队列,也就是队列里面只能有一个任务,加入队列就会被立刻执行。keepAliveTime=60说明当前线程在60秒内空闲则回收。

线程池操作源码

  • execute方法,执行任务。

    这段代码表明了大体的流程,就是如果核心线程数未到上线,首先创建核心线程执行;其次如果核心线程数已达到上限,那么将任务放到阻塞队列中;如果阻塞队列也满了,会创建新的线程去执行任务,也会检查是不是已经达到了允许的最大线程数,如果最大线程数也满了,会执行拒绝策略。

    • addWorker()

      第一部分通过双重循环增加线程数目,检查线程池状态。
      第二部分就是创建worker对象并且添加到工作集中并启动。 下面来说一下Worker这个比较重要的对象。首先来看一下这个类图对象,它继承了AQS,继承了Runable接口

      • 继承了AQS,说明他需要实现lock和unlock方法,也就是他自己简单实现了锁的功能,或者说他自己就是一个独占锁,这个其实是为了避免其他线程调用了线程池的shutdown方法,中断正在执行的worker线程,执行任务时会先进行woker.lock,这样会在执行完任务后在进行中断操作。因为shutdown方法只会中断,被阻塞挂起的空闲线程。
      • 继承了Runnable接口,说明他需要实现run()方法,因为worker本质上就是通过执行run方法来进行任务处理的。

      在上面start了worker中的线程之后,就会调用新建的woker中的run方法,run方法中会调用runWorker方法。

      在此就基本分析完了执行任务的操作,并且说明了线程池执行任务的原理。

  • shutDown方法关闭线程池。

    下面看一下设置中断标志的方法

    shutDown方法中调用tryterminate

  • awaitTermination方法

总结

线程池巧妙的利用了一个Integer的变量来标示线程池的状态以及线程池中线程的数量,通过线程池的状态来控制任务的执行,每个worker线程可以处理多个任务。