zoukankan      html  css  js  c++  java
  • Java-技术专区-Java线程池ThreadPoolExecutor的理解

    提高性能

    多个任务同步执行,提高性能。

    资源隔离,熔断,快速返回

    Spring Cloud 里面的 Hystrix 组件,就是基于线程池来做的熔断,资源隔离。

    每个请求都对应一个线程池,可以根据任务耗时及并发情况,调整线程池大小。将不同的请求隔离开来(比如:查询,交易,会员…),这样即使某个接口出现问题,不会耗尽系统资源,导致别的接口用不了。

    线程池=食堂吃饭排队

    其实软件就是现实世界的抽象表示。可以对比食堂吃饭排队来理解线程池。

    一个任务一个线程

      只要在食堂吃过饭的人都知道,食堂有好几个打饭的窗口。假如每次有人来吃饭开一个窗口,没人就关掉空闲的窗口,你们觉得这样合适吗?每个窗口需要一个分配一片场地,还要配备饭菜,打饭人员。可能一个人过来打个饭1分钟搞定。然后准备这个窗口却需要1个月。要是吃饭的人多了。现实世界不可能存在那么大的空间和资源来创建打饭窗口。

      对比软件世界就是:来一个任务创建一个线程。线程创建和打饭窗口创建一样需要大量的资源:,系统调用,内核态到用户态切换,分配,初始化线程堆栈等。每次任务执行都得等待线程创建。要是任务多,也会耗尽系统资源。

    灵活调整窗口数量

      大家都知道原来21楼食堂只有一个窗口,然后窗口的服务能力有限,我们公司人又多,还有其他公司的人也过来吃,结果导致有相当一部分同学吃不上饭,或者等很久才吃到。大家说这样是不是很不合理。好在后面食堂又增加了两个窗口,现在一共有3个窗口了。经过多次体验之后发现确实速度快很多。基本都能吃

    上饭了(虽然经常找不到座位,看样子座位也得增加了…)。

    对比软件世界就是:任务过多,多开几个线程处理。

      食堂比较聪明,他们发现中午吃饭的人比较多。晚上吃饭的人少。于是他们在晚上的时候就只开一个窗口,节约点员工开支,已经摆放菜品等工作。

      对比软件世界就是:设置核心线程和最大线程数,当排队人数排满等待队列后就会创建新的线程来处理,不会超过最大线程数 。

    不限制排队队列长度

      好,那么现在我们先创建好了几个打饭窗口,没有限制排队人数。结果导致排队了几千上万人。然后我们的打饭窗口速度有限,只能提供100人份饭菜。难不成后面排队的就一直在那干等?等到海枯石烂,饿死为止,又或者吵架闹事,拆食堂,导致一个人都吃不了饭?要是食堂能限制排队人数100为止,那该多好。至

    少后面来的人可以选择去别的地方吃饭,前面的人丝毫不受影响,依旧能有愉快的用餐体验。

      对比软件世界就是:ThreadPoolExecutor线程池里面的workQueue队列长度限制,不能太长,不然会导致任务堆积,处理缓慢。甚至OOM内存溢出。

    为什么需要线程池

      大家应该都知道线程创建成本是很高的

    系统调用,内核态到用户态切换

    分配,初始化线程堆栈等。

    因此每次任务过来new一个线程并不合理。

    通过线程池就可以复用事先创建好的线程,以提高性能。

    如何创建线程池

    阿里编程规范中有提到

      线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

    原因如下:

    1) FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

    2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

      Executors.newFixedThreadPool 指定了线程的数量,但是由于使用的LinkedBlockingQueue,由于没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE);如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被

    消耗殆尽了。

      Executors 下创建 cached thread pool,我们可以看出,Executors.newCachedThreadPool 并没有限定线程的数量,直接new ThreadPoolExecutor

    new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,TimeUnit.SECONDS,new LinkedBlockingQueue<>(maxBlockingQueue), new JlpayThreadFactory());

    通过查阅源码发现如下几个很重要的参数

    corePoolSize 核心线程数,无论什么情况都会保持的线程数

    maximumPoolSize 最大线程数,最大的线程数 ,当提交的任务大于corePoolSize 核心线程数 +workQueue 队列长度 后再提交的任务才会新增线程处理,不超过maximumPoolSize 最大线程数,workQueue 排队队列

    这个一定要设置队列长度,不然会导致OOM /任务处理等待时间过久

    如何配置合适的线程池参数

    corePoolSize 核心线程数 根据具体的业务情况(IO密集型,还是CPU密集型),通过压测找到最合适的线程数。我们的情况建议30-50。如果是比较空闲的业务可以设置少一些10个左右。

    maximumPoolSize 最大线程数 考虑频繁线程切换带来的开销,不能太大了。 如果是比较稳定的并发量,建议和corePoolSize 线程数一样,这样可以避免(maximumPoolSize -corePoolSize )个线程创建销毁的开销。

    workQueue 排队队列的长度 这个关键在于业务是否容忍响应慢的情况,排队多了,等待的时间会越长,建议在线程数的两倍以内。

  • 相关阅读:
    网络流(平面图转对偶图)
    666
    期望总结
    docker-1-简介
    22、整合mybatis
    21、整合Druid数据源
    20、Springboot 与数据访问(JDBC/自动配置)
    19、配置嵌入式servlet容器(下)
    18、配置嵌入式servlet容器(2)
    17、配置嵌入式servlet容器(1)
  • 原文地址:https://www.cnblogs.com/liboware/p/12881216.html
Copyright © 2011-2022 走看看