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

posted @ 2020-05-13 11:15  洛神灬殇  阅读(269)  评论(0编辑  收藏  举报