线程池的种类和使用场景

Executors.newCachedThreadPool

可根据实际情况,调整线程数量的线程池,线程池中的线程数量不确定,如果有空闲线程会优先选择空闲线程,如果没有空闲线程并且此时有任务提交会创建新的线程。在正常开发中并不推荐这个线程池,因为在极端情况下,会因为 newCachedThreadPool 创建过多线程而耗尽 CPU 和内存资源。
源码部分:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
// corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,keepAliveTime=60s,workQueue=SynchronousQueue(同步队列)

使用场景:
执行很多短期异步的小程序或者负载较轻的服务器
使用:

private static ExecutorService executorService = Executors.newCachedThreadPool();
 pool.execute(new Runnable() {
                public void run() {
                }
             });

Executors.newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

源码部分:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
// corePoolSize=n,maximumPoolSize=n,keepAliveTime=0s,WorkQueue为:new LinkedBlockingQueue() 无界阻塞队列

使用场景:
执行长期的任务,性能好很多
使用

private static ExecutorService executorService = Executors.newFixedThreadPool(5);
 pool.execute(new Runnable() {
                public void run() {
                }
             });

newSingleThreadExecutor

创建一个线程的线程池,在这个线程池中始终只有一个线程存在。如果线程池中的线程因为异常问题退出,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
源码部分:

 public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
// corePoolSize=1,maximumPoolSize=1,keepAliveTime=0s,workQueue为:new LinkedBlockingQueue() 无界阻塞队列

使用场景:
一个任务一个任务执行的场景
使用

private static ExecutorService executorService= Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
            public void run() {
            }
         });

newScheduledThreadPool

此线程池可以指定固定数量的线程来周期性的去执行。比如通过 scheduleAtFixedRate 或者 scheduleWithFixedDelay 来指定周期时间。PS:另外在写定时任务时(如果不用 Quartz 框架),最好采用这种线程池来做,因为它可以保证里面始终是存在活的线程的。
源码如下:

 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}
// corePoolSize=n,maximumPoolSize=Integer.MAX_VALUE,keepAliveTime=0s

使用场景:
周期性执行任务的场景

ThreadPoolExecutor

 ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1000));

几个参数含义
当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

设置参数原则
1.高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
2.并发不高、任务执行时间长的业务要区分开看:
a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务
b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和1一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换
3.并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考2。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦

设置线程池参数

1、默认值
* corePoolSize=1
* queueCapacity=Integer.MAX_VALUE
* maxPoolSize=Integer.MAX_VALUE
* keepAliveTime=60s
* allowCoreThreadTimeout=false
* rejectedExecutionHandler=AbortPolicy()

2、如何来设置
    * 需要根据几个值来决定
        - tasks :每秒的任务数,假设为500~1000
        - taskcost:每个任务花费时间,假设为0.1s
        - responsetime:系统允许容忍的最大响应时间,假设为1s
    * 做几个计算
        - corePoolSize = 每秒需要多少个线程处理? 
            * threadcount = tasks/(1/taskcost) =tasks*taskcout =  (500~1000)*0.1 = 50~100 个线程。corePoolSize设置应该大于50
            * 根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可
        - queueCapacity = (coreSizePool/taskcost)*responsetime
            * 计算可得 queueCapacity = 80/0.1*1 = 800。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
            * 切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
        - maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
            * 计算可得 maxPoolSize = (1000-800)/10 = 20
            * (最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数
        - rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
        - keepAliveTime和allowCoreThreadTimeout采用默认通常能满足

3、 以上都是理想值,实际情况下要根据机器性能来决定。如果在未达到最大线程数的情况机器cpu load已经满了,则需要通过升级硬件(呵呵)和优化代码,降低taskcost来处理。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值