我们都知道,在高并发的请求情况下,很多任务我们可能会采用多线程的方式来处理请求或者消费数据。多线程,顾名思义,很多的线程,如果我们每次都自己创建,然后在销毁线程,很麻烦,很浪费时间,举个不恰当的例子,一个线程做完自己的事后发现同伴还有活没做,完全可以帮着一起做,而不是赶紧下班跑了哈哈,说白了就是自己的事做完了,别着急跑,继续做其他事,让线程复用,来减少创建和销毁的次数,防止创建太多线程导致内存爆满。线程池就是做了这个事。
我们JAVA就提供了线程池,首先我们要明白几个线程池的核心参数和这几个参数实际在运行的时候是怎么协同起来的,最后在介绍下常用的几个线程池。执行过程如图所示:
(一)核心线程数:这个是线程池常备的线程数量,可以理解为,一个池子里基本上有这么多线程在等着干活,一个线程被创建出来,如果,线程数没达到线程池的核心线程数,每次都是新加的,且不会被释放掉,一直到线程池里线程的数量大于这个核心线程数,才根据空闲时间开始释放掉多余的线程资源,对于他的大小的设置,要区分你这个多线程的任务是,IO密集型还是CPU密集型。
IO密集型:这个任务大部分时间都是操作IO的,比如网络传输,文件读写啥的,这种类型的核心线程数应该我们一般设置成CPU的核数的二倍,为的是,当其他线程在操作IO耗时的时候,其他线程可以尽可能的利用CPU,做到不浪费CPU资源。
CPU密集型:顾名思义,比如计算量特别大的多线程任务,这个时候核心线程数的设置最好和CPU核数保持一致或者+1,为的是减少多个线程在计算的过程中频繁的切换,导致性能的下降。其实最合适的是自己先根据上面规则设置个大概,然后进行压力测试,做到权衡最好的那个适合自己的设置。
(二)任务队列:接着上面的,当一个线程池的核心线程数满了,才开始给队列里存任务,等待着去用核心线程执行,它的大小自己根据任务的多少来设置。
(三)最大线程数:再接着上面,核心线程数满了,当队列里的任务也满了,这时候看看线程池线程数达没达到最大线程数,没达到就继续创建线程来执行任务直到达到最大线程数。当池子里线程数大于核心线程数的时候,每当有线程空闲时间大于最大空闲时间,这个线程就会被释放掉,直到数量等于核心线程数的时候。
(四)拒绝策略:再接着上面的,当最大线程数也满了咋办?有拒绝策略给了四种选择,还可以自己定制如下:
AbortPolicy:满了还提交直接抛异常(RejectedExecutionException)。
CallerRunsPolicy:不抛异常,用触发开启线程的主线程来执行接下来的任务,可能会形成主线程的阻塞。
DiscardPolicy:无为而治,啥也不干,直接把新任务就丢了。
DiscardOldestPolicy:前浪死在沙滩上,把最老进去队列的任务给杀掉,自己加入进去。
定制拒绝策略:implements RejectedExecutionHandler然后重写rejectedExecution方法。
(五)常用线程池:
JAVA里提供了四个,三个底层都是直接调用ThreadPoolExecutor的:
newCachedThreadPool:
线程池无限大,比较适合短期的异步任务,负载低的情况,缺点也很明显,看不到具体开启了多少线程,有危险性,核心线程是0,最大线程数integer的最大值(一般没有机器可以到这个境界基本等于无限大),60秒有效期,60秒空闲时间有新的任务进来就用老线程运行,没有就释放掉了。
newFixedThreadPool:一个固定大小的线程池,队列是无限大,负载大的场景根据服务器性能设置线程池大小,源码可以看到,核心线程数和最大线程数相等,存活时间为0,注意这里的0不是0秒的意思,而是正无穷,换句话来说也就是一直存活,再看最后有一个工作队列,LinkedBlockingQueue<Runnable>这个是个正无穷的队列。
newSingleThreadExecutor:
一个单线程的线程池,所有的任务顺序执行和固定大小的区别就是核心线程数和最大线程数都是1。
newScheduledThreadPool:
一个可以延时执行或者重复执行的线程池。DelayedWorkQueue是一个无界队列,它能按一定的顺序对工作队列中的元素进行排列。
此外Spring里我们还常用到ThreadPoolTaskExecutor作为线程池,他其实也是在ThreadPoolExecutor的上面进行了封装源码如下:
最后咱们在贴一个常用的在spring里配置一个线程池的样例:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> 3 <!-- 线程池维护线程的最少数量 --> 4 <property name="corePoolSize" value="5" /> 5 <!-- 允许的空闲时间 --> 6 <property name="keepAliveSeconds" value="200" /> 7 <!-- 线程池维护线程的最大数量 --> 8 <property name="maxPoolSize" value="10" /> 9 <!-- 缓存队列 --> 10 <property name="queueCapacity" value="20" /> 11 <!-- 对拒绝task的处理策略 --> 12 <property name="rejectedExecutionHandler"> 13 <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /> 14 </property> 15 </bean>
最后,大家想获取更多知识的,可以继续关注公众号,不定时推送。分享了这么牛逼的知识,还不请小编喝个水吗,哈哈哈,欢迎土豪直接赏赞,谢谢,您的支持就是小编最大的动力。