之前曾给公司的小伙伴们讲过java线程池,遂把ppt拷过来;

线程池状态

java 线程池基于nacos动态配置线程池属性 java线程池demo_java线程池


RUNNING


线程池创建后就是running状态;

可以处理已添加的任务,可以新添加任务;


SHUTDOWN


调用线程池的shutdown()后;

可以处理已添加的任务(正在执行和队列里),不可以新加任务了;


STOP


running或者shutdown状态调用线程池的shutdownNow()后;

给正在执行的任务发中断指令,不处理队列里的任务,不可添加新任务;


TIDYING


shutdown状态队列为空并且线程池任务全完成,或者stop状态线程池任务全完成;

自动执行ThreadPoolExecutor类的terminated()方法,该方法是protected方法,可以重写自定义该方法;


TERMINATED


terminated()执行完毕后;

java里的线程池ThreadPoolExecutor类

构造方法有4个:

java 线程池基于nacos动态配置线程池属性 java线程池demo_java线程池_02

 继承关系:

java 线程池基于nacos动态配置线程池属性 java线程池demo_ThreadPool_03

 参数最多的构造方法:

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
                              long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

各个参数的解释如下:


corePoolSize :核心线程数( >=0 )


maximumPoolSize :最大线程数( >0 , >= corePoolSize )


keepAliveTime :空闲线程存活时间( >=0 ),默认只对超过核心线程数时有效,可以通过 allowCoreThreadTimeOut ( boolean ) 方法设置;


Unit :时间单位, java.util.concurrent.TimeUnit 枚举的单位,包括 NANOSECONDS (纳秒)、 MICROSECONDS (微秒)、 MILLISECONDS (毫秒)、 SECONDS (秒)、 MINUTES (分钟)、 HOURS (小时)、 DAYS (天);


workQueue :阻塞队列( not null )


直接提交队列: SynchronousQueue <E> ,这是一个没有容量的阻塞队列,插入数据的时候阻塞等待被取走,取数据的时候阻塞等待有数据插入;


有界队列: ArrayBlockingQueue <E> ,底层数组实现,必须设定大小,存取使用了一个锁,存取不创建销毁元素;


无界队列: LinkedBlockingQueue <E> ,底层链表实现,可不设定大小(不设定使用 int 最大值),存取两个锁,并发性更好,存取会创建销毁元素;


优先级队列: PriorityBlockingQueue <E> ,根据优先级排序取出;


threadFactory :线程工厂( not null ),有默认的线程工厂 Executors.defaultThreadFactory () ,也可以实现 ThreadFactory 接口重写 newThread 方法自定义创建线程过程;




java 线程池基于nacos动态配置线程池属性 java线程池demo_ThreadPool_04



java 线程池基于nacos动态配置线程池属性 java线程池demo_线程池_05


Handler :拒绝策略( not null ), ThreadPoolExecutor 提供了 4 种策略(内部类),我们也可以自己实现 RejectedExecutionHandler 接口自己处理;


AbortPolicy :直接抛出一个 RejectedExecutionException 异常(默认策略);


DiscardPolicy :丢弃该任务;


DiscardOldestPolicy :丢弃队列中最老的一个任务并重新提交该任务;


CallerRunsPolicy :用调用者线程执行该任务;


java 线程池基于nacos动态配置线程池属性 java线程池demo_ThreadPool_06

 

 ThreadPoolExecutor的运作方式:


当线程数小于 corePoolSize 时,直接创建线程执行,不管线程池里的空闲线程;


当线程数达到 corePoolSize 时,会向 workQueue 队列里添加该任务;


当队列无法添加时,并且线程数小于 maximumPoolSize ,则会继续创建新的线程执行任务;


当队列无法添加并且线程数达到 maximumPoolSize ,则会执行拒绝策略;


注:线程池中的线程都是相同的,并没有区分核心线程和非核心线程,只是用数字表示线程数量,当线程池里线程数量超过核心线程数量时,空闲线程会根据超时时间自己销毁,直到线程数降到核心线程数为止(默认情况),如果设置了allowCoreThreadTimeOut(true),则所有线程都会按照空闲超时时间销毁;

 

ThreadPoolExecutor的其他方法:

public void   allowCoreThreadTimeOut  (  boolean 
  value) 
 :设置允许核心线程超时销毁;
 
public   boolean  allowsCoreThreadTimeOut  ()
 
public   boolean  awaitTermination  (long timeout,  
 TimeUnit 
  unit) 
 :等待线程池 
 termination 
 ,超时返回 
 false 
 ,超时前 
 termination 
 返回 
 true 
 ;
 
public void execute(Runnable command)
 
public   int  getActiveCount  () 
 :返回正在执行任务的线程数的大概值;
 
public long   getCompletedTaskCount  ()  :返回已经完成的任务数量的大概值;
 
public   int  getCorePoolSize  ()
 
public long   getKeepAliveTime  (  TimeUnit 
  unit)
 
public   int  getLargestPoolSize  () 
 :返回线程池同一时间持有的最多线程数;
 
public   int  getMaximumPoolSize  ()
 
public   int  getPoolSize  () 
 :返回现在线程池中线程的数量;
 
public   BlockingQueue  <Runnable>   getQueue 
 ()
 
public   RejectedExecutionHandler  getRejectedExecutionHandler  ()
 
public long   getTaskCount  ()  :返回已经被安排任务总数大概值;
 
public   ThreadFactory  getThreadFactory  ()
 
public   boolean  isShutdown  ()
 
public   boolean  isTerminated  ()
 
public   boolean  isTerminating  ()
 
public   int  prestartAllCoreThreads  () 
 :启动所有核心线程,让它们空闲的等待工作,返回启动的数量;
 
public   boolean  prestartCoreThread  () 
 :启动一个核心线程空闲等待工作,如果所有核心线程都启动了则返回 
 false 
 ;
 
public void purge()  :尝试从队列中删除已经取消的任务,回收存储空间,任务取消后可能留在队列中等工作线程删除,不过有其他线程干扰时可能失败;
 
public   boolean   remove(Runnable task)
 
public void   setCorePoolSize  (  int 
 corePoolSize 
 )
 
public void   setKeepAliveTime  (long time,   TimeUnit 
  unit)
 
public void   setMaximumPoolSize  (  int 
 maximumPoolSize 
 )
 
public void   setRejectedExecutionHandler  (  RejectedExecutionHandler 
  handler)
 
public void   setThreadFactory  (  ThreadFactory 
 threadFactory 
 )
 
public void shutdown()
 
public List<Runnable>   shutdownNow  ()  :尝试停止正在执行的任务,停止处理等待处理的任务,返回队列里未执行的任务列表,但无法响应中断操作的任务不会终止;
 
public String   toString  ()
 
 
ThreadPoolExecutor类还有3个protected方法(都是空的方法,需要自己继承该类实现或者用匿名类实现):
 
protected void   beforeExecute  (Thread t, Runnable r)
 
protected void   afterExecute  (Runnable r,   Throwable 
  t)
 
protected void terminated()
 
 
还有继承父类AbstractExecutorService类的submit方法:
 
public Future<?> submit(Runnable task)
 
public <T> Future<T> submit(Runnable task, T result)
 
public <T> Future<T> submit(Callable<T> task)


Executors类提供的简单创建线程池的方法


Executors.newCachedThreadPool();
 
Executors.newFixedThreadPool(int nThreads);
 
Executors.newSingleThreadExecutor();
 
Executors.newScheduledThreadPool(int corePoolSize);
 
Executors.newSingleThreadScheduledExecutor  ();
 
Executors.newWorkStealingPool  ();


以上方法除了最后一个,前5个方法各有一个重载方法,多了一个threadFactory参数用来创建线程的工厂,newWorkStealingPool方法的重载方法参数是一个int值,用来表示并行性(跟cpu核心数相关);

阿里的java开发手册指出必须用线程池创建线程,并且不可以使用Executors提供的这些方法,要根据业务合理创建线程池,避免资源耗尽;

方法详解:

•newCachedThreadPool()

java 线程池基于nacos动态配置线程池属性 java线程池demo_创建线程_07

核心线程数为0,没有核心线程,队列是SynchronousQueue这个特殊的无容量阻塞队列,当没有空闲线程时所有任务都是直接创建非核心线程执行(会重用空闲线程),最多可以创建Integer最大值的线程数(资源消耗上限过大),超时时间60s,创建的非核心线程空闲超时60s都会自动销毁;

•newFixedThreadPool(int nThreads)

java 线程池基于nacos动态配置线程池属性 java线程池demo_创建线程_08

核心线程数和最大线程数都是参数nThreads,超时时间0ms,队列是无界阻塞队列(int最大值),所以最开始每来一个任务都是创建新线程执行任务,任务执行完毕新线程就空闲等待了,当线程数达到nThreads个时,再来任务就会向队列里添加任务,空闲等待的核心线程取走队列里的任务,由于队列足够大,最大线程数是失效状态,超时时间也是失效的状态;

•newSingleThreadExecutor()

java 线程池基于nacos动态配置线程池属性 java线程池demo_线程池_09

java 线程池基于nacos动态配置线程池属性 java线程池demo_创建线程_10

  

java 线程池基于nacos动态配置线程池属性 java线程池demo_线程池_11

使用内部类对ExecutorService的方法做了限制保证单线程,当线程故障时会重新申请一个线程代替故障线程;

•newScheduledThreadPool(int corePoolSize)

java 线程池基于nacos动态配置线程池属性 java线程池demo_ThreadPool_12

java 线程池基于nacos动态配置线程池属性 java线程池demo_java线程池_13

返回值ScheduledExecutorService是一个接口,定义了4个跟定时执行任务有关的方法;

java 线程池基于nacos动态配置线程池属性 java线程池demo_ThreadPool_14

返回的时候实例化了一个ScheduledThreadPoolExecutor类;

java 线程池基于nacos动态配置线程池属性 java线程池demo_java线程池_15

构造方法里调用了ThreadPoolExecutor的方法;

java 线程池基于nacos动态配置线程池属性 java线程池demo_线程池_16

 

java 线程池基于nacos动态配置线程池属性 java线程池demo_ThreadPool_17

DelayedWorkQueue是个基于数组的最小堆优先级阻塞队列(最顶点的值最小),每次取任务的时候总是把时间最靠前的取出来;

可调用方法有4个:


public   ScheduledFuture  <?> schedule(Runnable command, long delay,   TimeUnit 
  unit)
 
public <V>   ScheduledFuture  <V> schedule(Callable<V> callable, long delay,   TimeUnit 
  unit)


以上两个方法是延时执行任务;


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

在initialDelay时间后开始以固定时间频率period周期性执行任务;

下一次的开始时间=上一次的开始时间+period;

如果某次任务执行时间超过下次任务的开始时间,则该长时间任务结束后立即会执行下次任务(立即执行推迟的任务已保持固定频率);

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

在initialDelay时间后开始以固定时间延迟delay周期性执行任务;

下一次的开始时间=上一次的结束时间+delay;

注:这4个方法跟ThreadPoolExecutor执行过程略有不同,会先将任务放入队列,然后执行一个类似prestartCoreThread()的方法启动一个线程去队列获取任务(即使创建线程池时参数传入0也会启动一个线程去执行任务);

Timer和ScheduledExecutorService区别:


Timer 是基于绝对时间的,对系统的时间改变是敏感的,而 executor 是相对时间,修改系统时间不会造成影响;


Timer 不会捕获异常,如果 timertask 抛出未捕获异常将导致整个 T imer 取消,剩下的未执行的任务都无法执行,新任务也无法执行;


Timer 是单线程的,如果某个任务时间执行过长,会影响后面的其他任务的执行;


•newSingleThreadScheduledExecutor()

java 线程池基于nacos动态配置线程池属性 java线程池demo_线程池_18

 

java 线程池基于nacos动态配置线程池属性 java线程池demo_阻塞队列_19

跟newSingleThreadExecutor()原理基本一样;

•newWorkStealingPool()和newWorkStealingPool(int parallelism)

java 线程池基于nacos动态配置线程池属性 java线程池demo_线程池_20

java 线程池基于nacos动态配置线程池属性 java线程池demo_线程池_21

工作窃取线程池使用的是ForkJoinPool线程池(分治思想),线程用的ForkJoinWorkerThread类(继承了Thread),任务用的是ForkJoinTask类(两个常用子类:RecursiveAction和带返回值RecursiveTask<V>,继承并实现compute()方法编写自己的分治任务,还有个CountedCompleter<T>),执行的线程数跟cpu的核心数相关( 通常一个核心上跑一个线程,cpu并行执行任务,parallelism参数不能超过32767),该线程池适合cpu密集型任务,充分利用多cpu多核心的性能;

线程池的任务不是提交给线程池的队列,每个线程有个线程本地队列wokrQueue(双端队列),线程池会将任务平均分到每个线程的本地队列(尾),线程使用LIFO从本地队列取任务(尾),本地任务队列无任务则从其他线程队列按照FIFO取任务(头,优先查看曾经从自己队列窃取任务的线程队列),线程会顺序从队列取任务,不会因为当前任务阻塞而切换任务(不适用阻塞任务如I/O、线程同步、sleep等);

ForkJoinTask可以使用ForkJoinPool的invoke方法,普通任务也可以用普通线程池的方法(继承了AbstractExecutorService类);

线程池的线程是守护线程,如果不是cpu密集型任务耗时会比普通的线程池长,ForkJoinPool可以用有限数量的线程完成非常多的父子关系的任务,这比普通线程池优异;

 

java 线程池基于nacos动态配置线程池属性 java线程池demo_创建线程_22

 运行结果:

 

java 线程池基于nacos动态配置线程池属性 java线程池demo_java线程池_23