JUC线程池

线程池



前言

创建和切换线程的代价是非常大的。所以,我们需要能做到线程的复用——线程池。


一、线程池的作用

  1. 降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗;
  2. 提高响应速度:任务到达时不需要等待线程创建就可以立即执行;
  3. 提高线程的可管理性:线程池可以统一管理、分配、调优和监控。
    Java的线程池支持主要通过ThreadPoolExecutor来实现,我们使用的ExecutorService的各种线程池策略都是基于ThreadPoolExecutor实现的,所以ThreadPoolExecutor十分重要。

二、线程池状态

线程池有五种状态:running、shutdown、stop、tidying、terminated。

  privatefinalAtomicIntegerctl=new
AtomicInteger(ctlOf(RUNNING,0));
  privatestaticfinalintCOUNT_BITS=Integer.SIZE-3;
  privatestaticfinalintCAPACITY =(1<<COUNT_BITS)-1;
  //runStateisstoredinthehigh-orderbits
  privatestaticfinalintRUNNING  =-1<<COUNT_BITS;//对应
的高3位值是111
  privatestaticfinalintSHUTDOWN = 0<<COUNT_BITS;//对应
的高3位值是000
  privatestaticfinalintSTOP   = 1<<COUNT_BITS;//对应
的高3位值是001
  privatestaticfinalintTIDYING  = 2<<COUNT_BITS;//对应
的高3位值是010
  privatestaticfinalintTERMINATED= 3<<COUNT_BITS;//对应
的高3位值是011
  //Packingandunpackingctl
  privatestaticintrunStateOf(intc)  {returnc&
~CAPACITY;}
  privatestaticintworkerCountOf(intc){returnc&
CAPACITY;}
  privatestaticintctlOf(intrs,intwc){returnrs|wc;}

变量ctl定义为AtomicInteger,记录了“线程池中的任务数量”和“线程池的状态”两个信息。共32位,其中高3位表示“线程池状态”,低29位表示“线程池中的任务数量”。

  • running:处于running状态的线程池能够接受新任务,以及对新添加的任务进行处理;
  • shutdown:处于shutdown状态的线程池不可以接受新任务,但是可以对已添加的任务进行处理;
  • stop:处于stop状态的线程池不接受新任务,不处理已添加的任务,并且会中断正在处理的任务;
  • tidying:当所有的任务已终止,ctl记录的“任务数量”为0,线程池会变为tidying状态。当线程池变为tidying状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为tidying时,进行相应的处理;可以通过重载terminated()函数来实现;
  • terminated:线程池彻底终止的状态。
    在这里插入图片描述

三、线程池的各个参数

  1. corePoolSize:线程池中核心线程的数量。当提交一个任务时,线程池会新建一个线程来执行任务,直到当前线程数等于corePoolSize。如果调用了线程池的prestartAllCoreThreads()
    方法,线程池会提前创建并启动所有基本线程。
  2. maximumPoolSize:线程池中允许的最大线程数。线程池的阻塞队列满了之后,如果还有任务提交,如果当前的线程数小于maximumPoolSize,则会新建线程来执行任务。注意,如果使用的是无界队列,该参数也就没有什么效果了。
  3. keepAliveTime:线程空闲的时间。线程的创建和销毁是需要代价的。线程执行完任务后不会立即销毁,而是继续存活一段时间:keepAliveTime。默认情况下,该参数只有在线程数大于corePoolSize时才会生效。
  4. unit:keepAliveTime的单位。
  5. workQueue:用来保存等待执行的任务的BlockQueue阻塞队列,等待的任务必须实现Runnable接口。选择如下:
  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO;
  • LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO
  • PriorityBlockingQueue:具有优先级别的阻塞队列;
  • SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。
  1. threadFactory:用于设置创建线程的工厂。ThreadFactory的作用就是提供创建线程的功能的线程工厂。他是通过newThread()方法提供创建线程的功能,newThread()方法创建的线程都是“非守护线程”而且“线程优先级都是默认优先级”。
  2. handler:RejectedExecutionHandler,线程池的拒绝策略。当向线程池中提交任务时,如果此时线程池中的线程已经满了,而且阻塞队列也已经满了,则线程池会选择一种拒绝策略来处理该任务。
  • AbortPolicy:直接抛出异常,默认策略;
  • CallerRunsPolicy:用调用者所在的线程来执行任务;
  • DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务;
  • DiscardPolicy:直接丢弃任务;
  • 自定义拒绝策略:实现RejectedExecutionHandler接口即可。

四、线程池的种类

除了ThreadPoolExecutor可以创建线程池之外,Executor框架也提供了三种线程池,通过工具类Executors来创建。
另外还有一种ThreadPoolExecutor的线程池ScheduledThreadPoolExecutor,相当于提供了“延迟”和“周期执行”的功能。

1、FixedThreadPool

FixedThreadPool是复用固定数量的线程处理一个共享的无边界队列,定义如下:

public static ExecutorService newFixedThreadPool(int nThreads){
  return new ThreadPoolExecutor(nThreads,
  							   nThreads,
                 			   0L,TimeUnit.MILLISECONDS,
                 			   new LinkedBlockingQueue<Runnable>());
}

corePoolSize和maximumPoolSize都设置为创建FixedThreadPool时指定的参数nThreads,由于该线程池是固定线程数的线程池,当线程池中的线程数量等于corePoolSize时,如果继续提交任务,该任务会被添加到阻塞队列workQueue中,而workQueue使用的是LinkedBlockingQueue,但没有设置范围,那么则是最大值(Integer.MAX_VALUE),基本相当于一个无界队列。

2、SingleThreadExecutor

SingleThreadExecutor只会使用单个工作线程来执行一个无边界队列,定义如下:

public static ExecutorService newSingleThreadExecutor(){
  return new FinalizableDelegatedExecutorService(
  				new ThreadPoolExecutor(1,1,
                					  0L,TimeUnit.MILLISECONDS,
                					  newLinkedBlockingQueue<Runnable>()));
}

作为单一worker线程的线程池,它把corePool和maximumPoolSize均被设置为1,和FixedThreadPool一样使用的是无界队列LinkedBlockingQueue。
SingleThreadExecutor只会使用单个工作线程,它可以保证认为是按顺序执行的,任何时候都不会有多于一个的任务处于活动状态。注意,如果单个线程在执行过程中因为某些错误终止,新的线程会替代它执行后续线程。

3、CachedThreadPool

CachedThreadPool会根据需要,在线程可用时,重用之前构造好的池中线程,否则创建新线程,定义如下:

public static ExecutorService newCachedThreadPool(){
  return new ThreadPoolExecutor(0,Integer.MAX_VALUE,
                 			   60L,TimeUnit.SECONDS,
                 			   new SynchronousQueue<Runnable>());
}

它把corePool为0,maximumPoolSize为Integer.MAX_VALUE,这就意味着所有的任务一提交就会加入到阻塞队列中。因为线程池的基本大小设置为0,一般情况下线程池中没有线程,用的时候再创建。
但是keepAliveTime设置60,unit设置为秒,意味着空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。阻塞队列采用的SynchronousQueue,这是一个没有元素的阻塞队列。
这个线程池在执行大量短生命周期的异步任务时,可以显著提高程序性能。调用execute时,可以重用之前已构造的可用线程,如果不存在可用线程,那么会重新创建一个新的线程并将其加入到线程池中。如果线程超过60秒还未被使用,就会被终止并从缓存中移除。因此,线程池在长时间空闲后不会消耗任何资源。
但是这样就处理线程池会存在一个问题,如果主线程提交任务的速度远远大于CachedThreadPool的处理速度,则CachedThreadPool会不断地创建新线程来执行任务,这样有可能会导致系统耗尽CPU和内存资源,所以在使用该线程池时,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题。

4、ScheduledThreadPool

Timer与TimerTask虽然可以实现线程的周期和延迟调度,但是Timer与TimerTask存在一些问题:

  • Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,会发生一些缺陷;
  • 如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行;
  • Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化时会出现一些执行上的变化。
    为了解决这些问题,我们一般都是推荐ScheduledThreadPoolExecutor来实现。
    ScheduledThreadPoolExecutor继承了ThreadPoolExecutor且实现了ScheduledExecutorService接口,它就相当于提供了“延迟”和“周期执行”功能的ThreadPoolExecutor。
    ScheduledThreadPoolExecutor它可另行安排在给定的延迟后运行命令,或者定期执行命令。需要多个辅助线程时,或者要求ThreadPoolExecutor具有额外的灵活性或功能时,此类要优于Timer。
    在ScheduledThreadPoolExecutor的构造函数中,我们发现它都是利用ThreadLocalExecutor来构造的,唯一变动的地方就在于它所使用的阻塞队列变成了DelayedWordQueue。
    DelayedWordQueue为ScheduledThreadPoolExecutor中的内部类,类似于延时队列和优先级队列。在执行定时任务的时候,每个任务的执行时间都不同,所以DelayedWorkQueue的工作就是按照执行时间的升序来排列,执行时间距离当前时间越近的任务在队列的前面,这样就可以保证每次出队的任务都是当前队列中执行时间最靠前的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值