java中的线程池 ThreadPoolExecutor

线程池的基本工作流程和核心参数

线程池构造函数中的核心参数十分重要,理解好了这几个核心参数,是使用线程池和了解其原理的必要条件,因为不同的参数,会导致线程池的工作状态不同。

从ThreadPoolExecutor的构造函数可以看到,里面有7个核心参数,下面针对每个参数进行说明一下。

  • corePoolSize: 核心池大小(运行线程)
  • maximumPoolSize:池中允许的最大线程,这个参数表示了线程池中最多的线程数量,当队列满了之后,会判根据maximumPoolSize判断是否需要创建新的线程执行任务
  • keepAliveTime:当线程大于corePoolSize时,终止前多余的空闲线程等待新任务的最长时间
  • unit:上一个参数的时间单位
  • workQueue:存储还没来的及执行的任务,这个参数十分重要,因为使用不通的队列,会导致线程池不一样,例如使用了无界队列,那么永远不会进行扩容,所以需要知道java中队列的类型,并且根据实际情况,使用不同的队列
  • threadFactory:执行程序创建新线程时使用的工厂
  • handler:由于超出线程范围和队列容量而使执行被阻塞时使用的处理程序

对于corePoolSize、maximumPoolSize、workQueue三个参数,需要着重的进行理解,那么下面运行一个例子,不通情况下,线程池运行的情况,废话不多,撸代码。

首先定义一个工作线程

定义线程池,核心线程数1,最大线程数为2,队列为ArrayBlockingQueue有节队列大小为3。

运行结果如下:

可以看到,四个任务都是由一个先成为完成的,因为分发任务数=核心线程数+队列SIZE,所以不会创建线程进行处理任务。那么我们在添加一个任务会出现什么效果呢?

这时候会发现,有两个线程在工作,因为当向线程池中放入“任务5”的时候,线程池的队列已满,所以就会根据最大线程数,创建一个新的线程,进行工作。所以会存在2个线程工作的场景,同时如果线程2,在60秒钟没有工作的话,那么就会被销毁。

那么在放入“任务6”会出现什么效果呢?

这时候会抛出异常,因为我们的拒绝策略是AbortPolicy。

从上面的分析可以得出结论,线程池工作原理如下,首先查看核心线程是否还有空余,有就直接执行任务,当没有空余的时候,就放入队列。当队列满了之后,新来的任务就会根据最大线程数,进行线程的创建去执行任务(这里创建的线程都是根据参数设置时间进行回收的)。当超过最大线程数的时候,就会根据拒绝策略进行相关的操作。流程图如下:

核心参数中的核心线程数(corePoolSize),最大线程数(maximumPoolSize),从上面的描述已经很清楚,还有另外两个参数分别是阻塞队列、拒绝策略,这两个参数的不通会导致线程池操作的不通,例如队列使用了无界队列,那么整个线程池永远都不会进行扩容。

1、队列

java中提供了很多种队列,线程池中使用的时候阻塞队列(因为队列里面的任务是需要等待其他任务执行完毕才能执行,如果使用非阻塞队列,那么队列里面的任务永远都不会执行成功了),借着这个机会,同时介绍一个阻塞队列、非阻塞队列、双向队列。在介绍各种队列之前,先介绍几个名词。

有界、无界:就是队列是否存在固定大小,如果一个队列是有界的,那么在放入指定个数的元素后就无法再次放入了。如果一个队列是无界的,那么可以持续一直放入元素(底层也是有界队列,只不过当达到上限后,会扩容)。

阻塞、非阻塞:如果一个队列是阻塞的,在读取的时候当队列为空,会按照设置时间进行阻塞,添加的时候也会按时间进行阻塞。非阻塞则在添加、读取的时候不进行阻塞。

java中分为 Queue、Dueqe两种类型的队列,下面分别介绍一下它们的不同:

Queue :只能从头部取元素、插入元素到尾部(单向)

Deque:可以同时在头部、尾部插入和取出元素(双向)

Queue

非阻塞:

① PriorityQueue:无界优先级队列,放入元素必须能比较大小,否则抛出异常,按照自然大小排序。

② ConcurrentLinkedQueue :无界线程安全的队列,基于链表实现,可在多线线程访问中使用,(简单看了一下源码,采用CAS底层实现)

阻塞(BlockingQueue ):

① DelayQueue :无界阻塞延时队列,这里需要解释一下延时的作用,就是入列元素需要经过一段时间才能被访问。

② SynchronousQueue :同步队列,其实就是只有一个一个元素的阻塞队列。

③ LinkedBlockingQueue :基于链表的有界阻塞队列

④ ArrayBlockingQueue :基于数组的有界阻塞队列

⑤ PriorityBlockingQueue :无界优先级阻塞队列

Deque

非阻塞:

① LinkedList :我们常用的链表

② ArrayDeque:线程不安全的,基于数组的双向队列,大小可变

③ ConcurrentLinkedDeque:线程安全的无界双向队列

阻塞:

① LinkedBlockingDeque:基于链表的有界阻塞双向队列

上面简单的介绍了java中相关队列的实现,以及每个队列的特点,并没有进行源码分析和撸代码,毕竟是讲线程池,同时相信大家根据上面队列特性的不通,对线程池中 BlockingQueue 传递也有一定了解了。

2、拒绝策略

线程池拒绝策略一共有四种(他们的源码很简单,一目了然,是ThreadPoolExecutor的内部类),比较简单,它们分别是:

① AbortPolicy:线程池的默认策略,丢弃并抛出异常 RejectedExecutionException

② DiscardPolicy:直接丢弃任务

③ DiscardOldestPolicy:丢弃线程池中最老的任务,让最新的任务入列

④ CallerRunsPolicy:如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行

开发人员也可以根据自己的需要,自定义拒绝策略,只需要实现 RejectedExecutionHandler接口即可。

Executors工具类

为了更好的了解他们的组合使用,下面简单说明一下 Executors 这个类。这个类就是对ThreadPoolExecutor的构造函数进行简单封装,使用不同的参数实现不同的线程池功能,同时在生产中不建议使用该类,还是要根据自己实际的业务情况对参数进行设置。

1、newFixedThreadPool :固定大小的线程池,队列可以理解为无界的,因为默认值太大了,所以基本不会涉及到扩容,同时因为corePoolSize=maxPoolSize,也不会扩容。

2、newSingleThreadExecutor:整个线程池只有一个线程,因此队列中的任务可以按照顺序执行

3、newCachedThreadPool:没有核心线程,基本算是无限制的创建线程进行任务处理,当60秒钟没有执行任务的时候,就会回收线程。

4、ScheduledThreadPoolExecutor:按照执行的策略进行执行,可以看到最大线程接近无限大,队列采用延迟队列。

5、newWorkStealingPool  jdk1.8之后新增的线程池,底层用的ForkJoinPool 来实现的。 ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”分发到不同的cpu核心上执行,执行完后再把结果收集到一起返回。

工作窃取:就是闲置的CPU查看是否能帮忙其他的CPU执行任务。

源码分析,以看看这篇文章,非常棒写的很详细:传送门

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值