线程池核心原理

在JDK1.5中JUC引入了线程池,也给我们编程带来了极大的方便,在阿里的Java开发手册中也强制要求线程资源必须通过线程池提供,不允许在应用中显示创建线程。可以减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题,如果不使用线程池,可能会造成系统创建大量同类线程,导致内存过度。但是如果使用线程池不恰当的设置参数会无法达到预期的效果。

线程池的实现:

Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
   return new FinalizableDelegatedExecutorService
       (new ThreadPoolExecutor(1, 1,
                               0L, TimeUnit.MILLISECONDS,
                               new LinkedBlockingQueue<Runnable>()));
}
Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                            60L, TimeUnit.SECONDS,
                            new SynchronousQueue<Runnable>());
}
Executors.newFixedThreadPool(10);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>());
}
Executors.Executors.newScheduledThreadPool(10);
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

阿里的java开发手册中明确提示过不允许使用Executors去创建线程池,而应该通过ThreadPoolExecutor的方式去,这样的方式可以更加明确的了解线程池的运行规则,
FixedThreadPool和SingleThreadPool:允许请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
CachedThreadPool和ScheduledThreadPool:允许创建线程数量为Integer.AX_VALUE,可能创建大量的线程,从而导致OOM。

使用ThreadPoolExecutor创建线程池
public ThreadPoolExecutor(int corePoolSize,
                      int maximumPoolSize,
                     long keepAliveTime,
                     TimeUnit unit,
                     BlockingQueue<Runnable> workQueue,
                     ThreadFactory threadFactory,
                     RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

构造器中各个参数的含义:

  1. corePoolSize:核心线程池的大小。在创建线程池后,默认线程池找那个没有任何线程,而是等待有任务到来才创建线程去执行任务。(prestartAllCoreThreads()或者prestartCoreThread()方法)
  2. maximumPoolSize:线程池中最大线程数,表示线程池中最多能创建多少个线程
  3. KeepAliveTime:表示线程没有任务执行时保持多长时间终止 queueCapacity:任务队列容量(阻塞队列)
  4. RejectedExecutionHandler :拒绝策略

4种拒绝策略:

  1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出
  2. RejectedExecutionException异常
  3. ThreadPoolExecutor.CallerRunsPolicy:也是丢弃任务,但是不抛出异常
  4. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.DiscardPolicy:由调用线程处理该任务
线程池的执行任务过程如下:

  1. 当系统中线程数量小于核心线程数时,会创建线程。
  2. 当系统中线程数量大于核心线程数,并且任务队列未满(queueCapacity未满),将任务放入阻塞队列。
  3. 当系统中线程数量大于核心线程数,并且任务队列已满,先判断线程数是否小于最大线程数,如果小于就创建线程。如果等于最大线程数,则抛出异常,并执行拒绝策略。

如何合理的设置线程池的大小:

首先看两个概念:
  • CPU密集型:也叫计算机密集型,指系统的硬盘、内存比CPU要好很多,大部分情况下,系统运作时CPU的使用率100%,CPU在I/O读写时很快就可以完成。
  • IO密集型:和CPU密集型相反,指CPU的性能比内存、硬盘好很多,系统运行时大部分时间在进行读写操作,CPU使用率不是很高。
    对于CPU密集型任务应该配置尽可能小的线程,比如CPU数+1个线程数,IO密集型任务应该配置尽可能多的线程,因为IO操作不占用CPU,不要上CPU闲下来,如CPU数*2+1个线程数。
最佳线程数 = ((线程等待时间 + 线程CPU时间)/ 线程CPU时间)* CPU核数
		 =(线程等待时间与线程CPU时间之比 + 1* CPU核数 

比如:CPU运行时间0.5S,线程等待时间(IO消耗时间)1.5S,CPU核数8,则最佳线程数=((1.5+0.5)/0.5)*8=32
由此可见:线程等待时间占比越高,需要线程越多。CPU时间占比越高,需要线程越少。
还有一种方法设置是:

tasks:每秒需要处理的任务数;taskcost:每个任务花费时间;responsetime:系统容忍最大相应时间
核心线程数=每秒需要多少线程处理
		=tasks/(1/taskcout)=tasks*taskcout
考虑到二八法则还要
阻塞队列容量=(corepoolsize/taskcout)*responsetime
最大线程容量=(max(tasks)-阻塞队列容量)/(1/taskcost)

是否使用线程池就一定比单线程效率高呢?

答案是否定的,比如redis就是单线程的,但是却十分高效, 据说理论上操作可以达到百万量级/s。本质原因在于redis都是基于内存操作,可以更高效地利用CPU,而多线程会带来上下文切换开销,单线程并没有这种开销。多线程适用的场景一般是:存在相当比例的IO和网络操作。

有兴趣可以关注公众
可以关注公众号,每天一道大厂面试题,大家一起学习,一起成长。

参考资料: http://ifeve.com/how-to-calculate-threadpool-size

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值