说到并发编程,最关键的两个模块应该是锁和线程池,下面会详细地讲解Java中线程池的原理和自带的3种线程池。

一、ThreadPoolExecutor

  JDK自带的线程池是ThreadPoolExecutor,对一个线程池而言,有下面几个比较重要的参数:

  • corePoolSize(核心线程数):如果池中的实际线程数小于corePoolSize,无论是否有空闲的线程,都会给新的任务产生新的线程
  • maximumPoolSize(最大线程数):只有在workQueue是有界队列时才有效,如果corePoolSize<池中的线程数<maximumPoolSize,并且没有空闲线程,则给新任务产生新线程
  • workQueue(任务队列)
  • keepAliveTime(线程生存时间):如果线程数大于corePoolSize,并且有空闲线程,则在生存时间结束后中断空闲线程
  • allowCoreThreadTimeOut(是否允许核心线程中断):当设置为true时,如果核心线上是空闲的,那么同样会在线程生存时间后被中断,默认是false。
  • RejectedExecutionHandler(拒绝策略):在使用有界队列的时候,如果队列满了,就是用拒绝策略就是用来处理这些超出的任务。

二、常用的3中线程池

  当需要创建线程池时,除了直接通过 new ThreadPoolExecutor(…)来实现外,还可以使用Executors工具类给我们提供便捷方式。

Executors提供了3种类型的线程池:

  1. Executors.newSingleThreadExecutor()
  2. Executors.newFixedThreadPool(int n)
  3. Executors.newCachedThreadPool()

1、Executors.newSingleThreadExecutor()
corePoolSize=1
maximumPoolSize=1
keepAliveTime=0
workQueue=LinkedBlockingQueue
  创建一个仅有一个工作线程的线程池,任务队列使用LinkedBlockingQueue(无界队列)。由于使用无界队列,所以maximumPoolSize的大小意义不大,此处设置为1。

2、Executors.newFixedThreadPool(int n)
corePoolSize=n
maximumPoolSize=n
keepAliveTime=0
workQueue=LinkedBlockingQueue
  跟newSingleThreadExecutor一样,唯一不同的是工作线程的个数为n,由于使用了LinkedBlockingQueue,所以maximumPoolSize意义不大,设置为跟核心线程数一样大就行了。

3、Executors.newCachedThreadPool()
corePoolSize=0
maximumPoolSize=Integer.MAX_VALUE
keepAliveTime=60秒
workQueue=SynchronousQueue
  创建一个核心线程数为0,最大线程数为int最大值的线程池,任务队列使用SynchronousQueue。
每次有新任务时,会出现下面2中情况:
(1)没有空闲的工作线程,则立即创建一个工作线程
(2)有空闲工作线程,则获取一个工作线程,从SynchronousQueue中交换数据(也就是任务)
  由于在没有空闲工作线程时,每次都会创建一个新的工作线程,导致工作线程数量比较多,所以需要关闭那些60秒内没有工作的工作线程。

SynchronousQueue是一个阻塞队列,它内部没有容器,不存储任何数据,可参考:
https://zhuanlan.zhihu.com/p/29227508

使用场景:任务耗时较短、任务的处理速度大于任务的提交速度

三、拒绝策略

  在并发量超出预期的情况下,不管是使用哪种线程池,要么就是不停地创建线程来处理,要么就是不停地往任务队列添加任务。此时,如果没有使用拒绝策略,就很可能出现内存泄漏。所以,在进行线程池选型时,一定要添加拒绝策略。

java线程池提供了以下几种策略:

  1. AbortPolicy:线程池的默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
  2. DiscardPolicy:如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。
  3. DiscardOldestPolicy:如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。
  4. CallerRunsPolicy:如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行

当JDK自带的几种策略不能满足你的需求时,可以通过实现RejectedExecutionHandler接口来自定义拒绝策略。