java线程池不同场景下使用示例经验总结

目录
  • 引导语
  • 1、coreSize == maxSize
  • 2、maxSize 无界 + SynchronousQueue
  • 3、maxSize 有界 + Queue 无界
  • 4、maxSize 有界 + Queue 有界
  • 5、keepAliveTime 设置无穷大
  • 6、线程池的公用和独立
  • 7、如何算线程大小和队列大小
  • 8、总结

引导语

ThreadPoolExecutor 初始化时,主要有如下几个参数:

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

大家对这几个参数应该都很熟悉了,虽然参数很少,但实际工作中却有很多门道,大多数的问题主要集中在线程大小的设置,队列大小的设置两方面上,接下来我们一起看看工作中,如何初始化 ThreadPoolExecutor。

1、coreSize == maxSize

我相信很多人都看过,或自己写过这样的代码:

ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 600000L, TimeUnit.DAYS,
                                                     new LinkedBlockingQueue());

这行代码主要展示了在初始化 ThreadPoolExecutor 的时候,coreSize 和 maxSize 是相等的,这样设置的话,随着请求的不断增加,会是这样的现象:

  • 请求数 < coreSize 时,新增线程;
  • 请求数 >= coreSize && 队列不满时,添加任务入队;
  • 队列满时,此时因为 coreSize 和 maxSize 相等,任务会被直接拒绝。

这么写的最大目的:是想让线程一下子增加到 maxSize,并且不要回收线程,防止线程回收,避免不断增加回收的损耗,一般来说业务流量都有波峰低谷,在流量低谷时,线程不会被回收;流量波峰时,maxSize 的线程可以应对波峰,不需要慢慢初始化到 maxSize 的过程。

这样设置有两个前提条件:

allowCoreThreadTimeOut 我们采取默认 false,而不会主动设置成 true,allowCoreThreadTimeOut 是 false 的话,当线程空闲时,就不会回收核心线程;

keepAliveTime 和 TimeUnit 我们都会设置很大,这样线程空闲的时间就很长,线程就不会轻易的被回收。

我们现在机器的资源都是很充足的,我们不用去担心线程空闲会浪费机器的资源,所以这种写法目前是很常见的。

2、maxSize 无界 + SynchronousQueue

在线程池选择队列时,我们也会看到有同学选择 SynchronousQueue,SynchronousQueue 我们在 《SynchronousQueue 源码解析》章节有说过,其内部有堆栈和队列两种形式,默认是堆栈的形式,其内部是没有存储的容器的,放元素和拿元素是一一对应的,比如我使用 put 方法放元素,如果此时没有对应的 take 操作的话,put 操作就会阻塞,需要有线程过来执行 take 操作后,put 操作才会返回。

基于此特点,如果要使用 SynchronousQueue 的话,我们需要尽量将 maxSize 设置大一点,这样就可以接受更多的请求。

假设我们设置 maxSize 是 10 的话,选择 SynchronousQueue 队列,假设所有请求都执行 put 操作,没有请求执行 take 操作,前 10 个 put 请求会消耗 10 个线程,都阻塞在 put 操作上,第 11 个请求过来后,请求就会被拒绝,所以我们才说尽量把 maxSize 设置大一点,防止请求被拒绝。

maxSize 无界 + SynchronousQueue 这样的组合方式优缺点都很明显:

优点:

当任务被消费时,才会返回,这样请求就能够知道当前请求是已经在被消费了,如果是其他的队列的话,我们只知道任务已经被提交成功了,但无法知道当前任务是在被消费中,还是正在队列中堆积。

缺点:

比较消耗资源,大量请求到来时,我们会新建大量的线程来处理请求;

如果请求的量难以预估的话,maxSize 的大小也很难设置。

3、maxSize 有界 + Queue 无界

在一些对实时性要求不大,但流量忽高忽低的场景下,可以使用 maxSize 有界 + Queue 无界的组合方式。

比如我们设置 maxSize 为 20,Queue 选择默认构造器的 LinkedBlockingQueue,这样做的优缺点如下:

优点:

电脑 cpu 固定的情况下,每秒能同时工作的线程数是有限的,此时开很多的线程其实也是浪费,还不如把这些请求放到队列中去等待,这样可以减少线程之间的 CPU 的竞争;

LinkedBlockingQueue 默认构造器构造出来的链表的最大容量是 Integer 的最大值,非常适合流量忽高忽低的场景,当流量高峰时,大量的请求被阻塞在队列中,让有限的线程可以慢慢消费。

缺点:

流量高峰时,大量的请求被阻塞在队列中,对于请求的实时性难以保证,所以当对请求的实时性要求较高的场景,不能使用该组合。

4、maxSize 有界 + Queue 有界

这种组合是对 3 缺点的补充,我们把队列从无界修改成有界,只要排队的任务在要求的时间内,能够完成任务即可。

这种组合需要我们把线程和队列的大小进行配合计算,保证大多数请求都可以在要求的时间内,有响应返回。

5、keepAliveTime 设置无穷大

有些场景下我们不想让空闲的线程被回收,于是就把 keepAliveTime 设置成 0,实际上这种设置是错误的,当我们把 keepAliveTime 设置成 0 时,线程使用 poll 方法在队列上进行超时阻塞时,会立马返回 null,也就是空闲线程会立马被回收。

所以如果我们想要空闲的线程不被回收,我们可以设置 keepAliveTime 为无穷大值,并且设置 TimeUnit 为时间的大单位,比如我们设置 keepAliveTime 为 365,TimeUnit 为 TimeUnit.DAYS,意思是线程空闲 1 年内都不会被回收。

在实际的工作中,机器的内存一般都够大,我们合理设置 maxSize 后,即使线程空闲,我们也不希望线程被回收,我们常常也会设置 keepAliveTime 为无穷大。

6、线程池的公用和独立

在实际工作中,某一个业务下的所有场景,我们都不会公用一个线程池,一般有以下几个原则:

查询和写入不公用线程池,互联网应用一般来说,查询量远远大于写入的量,如果查询和写入都要走线程池的话,我们一定不要公用线程池,也就是说查询走查询的线程池,写入走写入的线程池,如果公用的话,当查询量很大时,写入的请求可能会到队列中去排队,无法及时被处理;

多个写入业务场景看情况是否需要公用线程池,原则上来说,每个业务场景都独自使用自己的线程池,绝不共用,这样在业务治理、限流、熔断方面都比较容易,一旦多个业务场景公用线程池,可能就会造成业务场景之间的互相影响,现在的机器内存都很大,每个写入业务场景独立使用自己的线程池也是比较合理的;

多个查询业务场景是可以公用线程池的,查询的请求一般来说有几个特点:查询的场景多、rt 时间短、查询的量比较大,如果给每个查询场景都弄一个单独的线程池的话,第一个比较耗资源,第二个很难定义线程池中线程和队列的大小,比较复杂,所以多个相似的查询业务场景是可以公用线程池的。

7、如何算线程大小和队列大小

在实际的工作中,我们使用线程池时,需要慎重考虑线程的大小和队列的大小,主要从几个方面入手:

  • 根据业务进行考虑,初始化线程池时,我们需要考虑所有业务涉及的线程池,如果目前所有的业务同时都有很大流量,那么在对于当前业务设置线程池时,我们尽量把线程大小、队列大小都设置小,如果所有业务基本上都不会同时有流量,那么就可以稍微设置大一点;
  • 根据业务的实时性要求,如果实时性要求高的话,我们把队列设置小一点,coreSize == maxSize,并且设置 maxSize 大一点,如果实时性要求低的话,就可以把队列设置大一点。

假设现在机器上某一时间段只会运行一种业务,业务的实时性要求较高,每个请求的平均 rt 是 200ms,请求超时时间是 2000ms,机器是 4 核 CPU,内存 16G,一台机器的 qps 是 100,这时候我们可以模拟一下如何设置:

4 核 CPU,假设 CPU 能够跑满,每个请求的 rt 是 200ms,就是 200 ms 能执行 4 条请求,2000ms 内能执行 2000/200 * 4 = 40 条请求;

200 ms 能执行 4 条请求,实际上 4 核 CPU 的性能远远高于这个,我们可以拍脑袋加 10 条,也就是说 2000ms 内预估能够执行 50 条;

一台机器的 qps 是 100,此时我们计算一台机器 2 秒内最多处理 50 条请求,所以此时如果不进行 rt 优化的话,我们需要加至少一台机器。

线程池可以大概这么设置:

ThreadPoolExecutor executor = new ThreadPoolExecutor(15, 15, 365L, TimeUnit.DAYS,
                                                     new LinkedBlockingQueue(35));

线程数最大为 15,队列最大为 35,这样机器差不多可以在 2000ms 内处理最大的请求 50 条,当然根据你机器的性能和实时性要求,你可以调整线程数和队列的大小占比,只要总和小于 50 即可。

以上只是很粗糙的设置,在实际的工作中,还需要根据实际情况不断的观察和调整。

8、总结

线程池设置非常重要,我们尽量少用 Executors 类提供的各种初始化线程池的方法,多根据业务的量,实时性要求来计算机器的预估承载能力,设置预估的线程和队列大小,并且根据实时请求不断的调整线程池的大小值。

以上就是java线程池不同场景下使用示例经验总结的详细内容,更多关于java线程池不同场景使用经验的资料请关注我们其它相关文章!

(0)

相关推荐

  • 学生视角手把手带你写Java 线程池

    目录 Java手写线程池(第一代) 手写线程池-定义参数 手写线程池-构造器 手写线程池-默认构造器 手写线程池-execute方法 手写线程池-处理任务 手写线程池-优雅关闭线程池 手写线程池-暴力关闭线程池 手写线程池-源代码 问题 Java手写线程池(第一代) 经常使用线程池,故今天突发奇想,手写一个线程池,会有很多不足,请多多宽容.因为这也是第一代的版本,后续会更完善. 手写线程池-定义参数 private final AtomicInteger taskcount=new Atomic

  • Java自定义线程池的实现示例

    目录 一.Java语言本身也是多线程,回顾Java创建线程方式如下: 二.JDK线程池工具类. 三.业界知名自定义线程池扩展使用. 一.Java语言本身也是多线程,回顾Java创建线程方式如下: 1.继承Thread类,(Thread类实现Runnable接口),来个类图加深印象. 2.实现Runnable接口实现无返回值.实现run()方法,啥时候run,黑话了. 3.实现Callable接口重写call()+FutureTask获取. public class CustomThread {

  • 学生视角手把手带你写Java 线程池改良版

    目录 Java手写线程池(第二代) 第二代线程池的优化 线程池构造器 线程池拒绝策略 execute方法 手写线程池源码 MyExecutorService MyRejectedExecutionException MyRejectedExecutionHandle 核心类MyThreadPoolExecutor 线程池测试类 Java手写线程池(第二代) 第二代线程池的优化 1:新增了4种拒绝策略.分别为:MyAbortPolicy.MyDiscardPolicy.MyDiscardOldes

  • java线程池使用及原理面试题

    目录 引导语 1.说说你对线程池的理解? 2.ThreadPoolExecutor.Executor.ExecutorService.Runnable.Callable.FutureTask 之间的关系? 3.说一说队列在线程池中起的作用? 4.结合请求不断增加时,说一说线程池构造器参数的含义和表现? 5.coreSize 和 maxSize 可以动态设置么,有没有规则限制? 6.说一说对于线程空闲回收的理解,源码中如何体现的? 7.如果我想在线程池任务执行之前和之后,做一些资源清理的工作,可以

  • 非常适合新手学生的Java线程池超详细分析

    目录 线程池的好处 创建线程池的五种方式 缓存线程池CachedThreadPool 固定容量线程池FixedThreadPool 单个线程池SingleThreadExecutor 定时任务线程池ScheduledThreadPool ThreadPoolExecutor创建线程池(十分推荐) ThreadPoolExecutor的七个参数详解 workQueue handler 如何触发拒绝策略和线程池扩容? 线程池的好处 可以实现线程的复用,避免重新创建线程和销毁线程.创建线程和销毁线程对

  • Java线程池必知必会知识点总结

    目录 1.线程数使用开发规约 2.ThreadPoolExecutor源码 1.构造函数 2.核心参数 3.execute()方法 3.线程池的工作流程 4.Executors创建返回ThreadPoolExecutor对象(不推荐) 1.Executors#newCachedThreadPool=>创建可缓存的线程池 2.Executors#newSingleThreadExecutor=>创建单线程的线程池 3.Executors#newFixedThreadPool=>创建固定长度

  • Java线程池7个参数的详细含义

    目录 一.corePoolSize线程池核心线程大小 二.maximumPoolSize线程池最大线程数量 三.keepAliveTime空闲线程存活时间 四.unit空闲线程存活时间单位 五.workQueue工作队列 六.threadFactory线程工厂 七.handler拒绝策略 java多线程开发时,常常用到线程池技术,这篇文章是对创建java线程池时的七个参数的详细解释. 从源码中可以看出,线程池的构造函数有7个参数 这 7 个参数分别是: corePoolSize:核心线程数. m

  • Java线程池实现原理总结

    目录 一.线程池参数 二.线程池执行流程 三.四种现成的线程池 要理解实现原理,必须把线程池的几个参数彻底搞懂,不要死记硬背 一.线程池参数 1.corePoolSize(必填):核心线程数. 2.maximumPoolSize(必填):最大线程数. 3.keepAliveTime(必填):线程空闲时长.如果超过该时长,非核心线程就会被回收. 4.unit(必填):指定keepAliveTime的时间单位.常用的有:TimeUnit.MILLISECONDS(毫秒).TimeUnit.SECON

  • java线程池不同场景下使用示例经验总结

    目录 引导语 1.coreSize == maxSize 2.maxSize 无界 + SynchronousQueue 3.maxSize 有界 + Queue 无界 4.maxSize 有界 + Queue 有界 5.keepAliveTime 设置无穷大 6.线程池的公用和独立 7.如何算线程大小和队列大小 8.总结 引导语 ThreadPoolExecutor 初始化时,主要有如下几个参数: public ThreadPoolExecutor(int corePoolSize, int

  • java线程池工作队列饱和策略代码示例

    线程池(Thread Pool) 是并行执行任务收集的实用工具.随着 CPU 引入适合于应用程序并行化的多核体系结构,线程池的作用正日益显现.通过 ThreadPoolExecutor类及其他辅助类,Java 5 引入了这一框架,作为新的并发支持部分. ThreadPoolExecutor框架灵活且功能强大,它支持特定于用户的配置并提供了相关的挂钩(hook)和饱和策略来处理满队列 Java线程池会将提交的任务先置于工作队列中,在从工作队列中获取(SynchronousQueue直接由生产者提交

  • 到底如何设置Java线程池的大小的方法示例

    在我们日常业务开发过程中,或多或少都会用到并发的功能.那么在用到并发功能的过程中,就肯定会碰到下面这个问题 并发线程池到底设置多大呢? 通常有点年纪的程序员或许都听说这样一个说法 (其中 N 代表 CPU 的个数) CPU 密集型应用,线程池大小设置为 N + 1 IO 密集型应用,线程池大小设置为 2N 这个说法到底是不是正确的呢? 其实这是极不正确的.那为什么呢? 首先我们从反面来看,假设这个说法是成立的,那我们在一台服务器上部署多少个服务都无所谓了.因为线程池的大小只能服务器的核数有关,所

  • java线程池不同场景如何使用及示例经验总结

    https://blog.csdn.net/qq_34272760/article/details/120648262?spm=1001.2014.3001.5502

  • Java线程池FutureTask实现原理详解

    前言 线程池可以并发执行多个任务,有些时候,我们可能想要跟踪任务的执行结果,甚至在一定时间内,如果任务没有执行完成,我们可能还想要取消任务的执行,为了支持这一特性,ThreadPoolExecutor提供了 FutureTask 用于追踪任务的执行和取消.本篇介绍FutureTask的实现原理. 类视图 为了更好的理解FutureTask的实现原理,这里先提供几个重要接口和类的结构,如下图所示: RunnableAdapter ThreadPoolExecutor提供了submit接口用于提交任

  • java线程池ThreadPoolExecutor的八种拒绝策略示例详解

    目录 池化设计思想 线程池触发拒绝策略的时机 JDK内置4种线程池拒绝策略 拒绝策略接口定义 AbortPolicy(中止策略) DiscardPolicy(丢弃策略) DiscardOldestPolicy(弃老策略) 第三方实现的拒绝策略 Dubbo 中的线程拒绝策略 Netty 中的线程池拒绝策略 ActiveMQ 中的线程池拒绝策略 PinPoint 中的线程池拒绝策略 谈到 Java 的线程池最熟悉的莫过于 ExecutorService 接口了,jdk1.5 新增的 java.uti

  • JAVA线程池专题(概念和作用)

    线程池的作用 我们在用一个东西的时候,首先得搞明白一个问题.这玩意是干嘛的,为啥要用这个,用别的不行吗.那么一个一个解决这些问题 我们之前都用过数据库连接池,线程池的作用和连接池有点类似,频繁的创建,销毁线程会造成大量的不必要的性能开销,所以这个时候就出现了一个东西统一的管理线程,去负责线程啥时候销毁,啥时候创建,以及维持线程的状态,当程序需要使用线程的时候,直接从线程池拿,当程序用完了之后,直接把线程放回线程池,不需要去管线程的生命周期,专心的执行业务代码就行. 当然,如果非要是自己想手动ne

  • 深入理解Java线程池从设计思想到源码解读

    线程池:从设计思想到源码解析 前言初识线程池线程池优势线程池设计思路 深入线程池构造方法任务队列拒绝策略线程池状态初始化&容量调整&关闭 使用线程池ThreadPoolExecutorExecutors封装线程池 解读线程池execute()addWorker()Worker类runWorker()processWorkerExit() 前言 各位小伙伴儿,春节已经结束了,在此献上一篇肝了一个春节假期的迟来的拜年之作,希望读者朋友们都能有收获. 根据穆氏哲学,投入越多,收获越大.我作此文时

  • 超详细讲解Java线程池

    目录 池化技术 池化思想介绍 池化技术的应用 如何设计一个线程池 Java线程池解析 ThreadPoolExecutor使用介绍 内置线程池使用 ThreadPoolExecutor解析 整体设计 线程池生命周期 任务管理解析 woker对象 Java线程池实践建议 不建议使用Exectuors 线程池大小设置 线程池监控 带着问题阅读 1.什么是池化,池化能带来什么好处 2.如何设计一个资源池 3.Java的线程池如何使用,Java提供了哪些内置线程池 4.线程池使用有哪些注意事项 池化技术

  • java线程池ThreadPoolExecutor类使用详解

    在<阿里巴巴java开发手册>中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量:另一方面线程的细节管理交给线程池处理,优化了资源的开销.而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool().newSingleThreadExecutor().newCachedThreadPool(

随机推荐