提高性能
多个任务同步执行,提高性能。
资源隔离,熔断,快速返回
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 排队队列的长度 这个关键在于业务是否容忍响应慢的情况,排队多了,等待的时间会越长,建议在线程数的两倍以内。