Java线程池

基础知识

Java线程池可以先充一个构造函数说起(当然最好的方式是看源码,本文的理解,主要来自于源码上的注释)。

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

字段说明

  • corePoolSize

    核心线程数,一个线程池里面时刻保持的线程数,即便他是空闲的,除非设置了allowCoreThreadTimeOut这个参数。ps:一个线程池的核心线程数最填满,如果当前线程池的核心线程数小于这个值,那么每次新来了一个线程任务,会开启一个新的线程来处理,直到达到核心线程数目

  • maximumPoolSize
    最大线程数,一个线程池里面允许的最大的线程数量!当队列满了的时候,并且有超过核心线程数小于最大线程数的时候,就会创建一个新的线程去处理任务,处理完成之后会过期新增的线程!这里需要注意,只有有界队列才会触发最大线程数,如果是无界队列,那么不会生效,因为不会满,这里需要特别注意!

  • keepAliveTime
    当前线程数大于核心线程数,其余的线程过期时间

  • unit
    是keepAliveTime的TimeUnit

  • workQueue
    工作队列,这个队列主要是保存未执行的任务,当任务已提交就会默认到这个队列里面。对于队列的选择有三种:
    同步队列SynchronousQueue:同步队列好处是不需要做任务的保留,来了一个任务直接新建一个线程去处理,如果线程不够,会新增一个线程,而不需要先放到队列里面去,这样会减少数据在内存中的同步,并且能够减少内部数据依赖产生的锁!但这个队列适合无界的队列来避免任务被拒绝掉,当然这样会导致线程无线增长!
    无界队列LinkedBlockingQueue:当使用无界队列的时候,maximumPoolSize参数将会不起作用,这样做的坏处是请求无线大的时候会消耗大量的内存
    有界队列ArrayBlockingQueue:当使用有界队列的时候,maximumPoolSize参数将会起作用,有界队列的好处是会控制资源的大小,坏处是有可能会会限制吞吐量,特别是处理那种需要很长时间的IO的任务的时候!

  • threadFactory
    线程工厂,可以自定义一个线程工厂进去,主要的职能是生成一个线程,可以给线程声明一些自定义属性,如果不传会用默认的线程池

  • handler
    拒绝策略,拒绝策略有两种情况会生效,线程池被关闭、使用有界队列并且队列饱和以及达到最大线程数!JDK提供了4中拒绝策略:
    AbortPolicy:默认的拒绝策略,通过抛出一个运行时异常(RejectedExecutionException)来拒绝任务的提交
    CallerRunsPolicy:当前线程自己去执行这个任务,当使用这个策略的时候,主线程提交给线程池的时候,会自己去执行自己的任务
    DiscardPolicy:这个就是最简单的策略,直接丢弃当前任务
    DisCardOldestPolicy:这个策略生效的前提是线程池未关闭,这样的会就会丢弃最前面的任务
    自定义拒绝策略:有时候给的这种默认的策略也不满足我们自己的需求,因此可以自定义自己的拒绝策略方式!只需要实现接口RejectedExecutionHandler即可!

常用线程池

JDK提供了很多功能强大的线程池的类型,当然我们再日常开发中也可以根据自己的需求自定义线程池,当然原则是通过上面一节的参数讲解,下面就讲讲讲JDK提供的常用线程池:

普通线程池(线程没有复杂的调度逻辑)

  • newFixedThreadPool:固定核心线程数线程池,默认采取无界LinkedBlockingQueue,并且设置线程过期时间为0,也就意味会一直保持核心线程数的线程,减少线程新建的开销,本线程池适用于稳定的QPS这种类型,因为会活跃一批线程数,并且线程池不会被回收,JDK提供了两个工厂方法,一个是传一个核心线程数,另外一个是传一个线程工厂加一个核心线程数

  • newSingleThreadExecutor:这个线程池是一个单线程的线程池,和固定线程数的线程池一样,只是核心线程数为1,适合于那种有序的任务类的调度;

  • newCacheThreadPool:这个线程池和固定线程池的区别是,队列用SynchronousQueue同步队列,是一种直接投递的线程池,新来了一个任务直接新建一个线程来处理,而不需要通过中间容器来分发和中转,最大限度的提升性能,另外,新建的线程如果在60s没有使用的话会自动回收,这种线程池适合于那种请求不多,并且不均匀的业务场景,如果是那种持续的大量请求的业务场景则不建议用,因为这个会理论上会无线递增线程数

调度线程池

  • newScheduledThreadPool:调度线程池,同固定核心线程数的线程池一样,会一直保持活跃的核心线程数,不过阻塞队列用了DelayedWorkQueue
  • newSingleThreadScheduledExecutor:单线程调度线程池,对应上面的单线程固定线程池
  • newWorkStealingPool:工作窃取线程池,是调ForkJoinPool线程池,任务通过fork+join来调度,使用分治思想来创建

线程池的使用

这个就简单分享下使用线程池需要思考的几个地方吧

  • 如何设置corePoolSize?
    核心线程数的考量主要是要结合对应的机器,判断业务的类型,比如是IO密集型(IO任务的特征是线程会经常阻塞,这样就可以多使用一些线程数,按照经验来说2n+1是建议推荐的,如果想达到性能的极致这个就比较复杂,会涉及到平均任务的调度时间和任务数,有兴趣的可以参考下《Java并发实战》)还是CPU密集型(不会频繁的阻塞以及现场的长时间挂起,这个就推荐n+1,注意n为cpu的核数)

  • 选择一个合适的阻塞队列
    队列的选择可以是有界还是无界,无界队列一般在项目中资源不可控,推荐还是选择有界队列,并且增加和一个合适的拒绝策略(重新定义RejectedExecutionHandler),根据业务情况,是否拒绝还是延迟消费,如果延迟消费的话建议通过中间件将消息转发到另外的消费组里面去,当然也可以使用延迟队列去处理,根据自身业务情况的考量!也欢迎大家补充

  • 是否需要对线程做到有效监控
    在有的业务场景,我们可能会关注到线程的执行情况,这里可以通过自己定义threadFactory,里面重写下线程的某些状态转移情况

  • 是否需要考虑调度的顺序以及幂等性
    有时候我们也可能会关注某几种任务或者请求的调度顺序或者幂等,这里需要结合自身的经验来处理

  • 不用的线程是否需要释放
    这个考虑点主要在于节省系统的资源,可以通过设置超时时间来处理

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值