线程池

JDK自带的创建的线程池:

// 单线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//创建固定大小的线程,一般是按照多少核CPU就写多少个。
ExecutorService executorService2=Executors.newFixedThreadPool(10);
// 创建缓存线程,当某个线程执行完的时候会复用这个线程,而不用再次创建线程
ExecutorService executorService3 = Executors.newCachedThreadPool();

 

跟踪newSingleThreadExecutor 对应的源码可以发现:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));

 默认创建1个核心线程,总线程池大小为1,采用LinkedBlockingQueue堵塞队列,其中堵塞队列大小默认为2^31 -1,相当于*队列,并且没有采用拒绝策略,假设处理一个任务要很长时间,而此时该堵塞队列堆积了很多任务,就会导致内存飙升,最终导致OOM内存溢出的异常。

同理,newFixedThreadPool的源码也是如此。

而newCachedThreadPool则不一样,

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

  newCachedThreadPool的核心线程数为0,但是线程池总数却是2^31 -1,比如有1000万个线程,那么就会创建1000W个线程,最后会导致CPU高达100%。

线程影响CPU,队列影响内存。

 

 开发环境创建线程池方法:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor();

  其中里面有完整的参数:

ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

  corePoolSize:核心线程数,优先提交及优先执行。

maximumPoolSize:线程池总数。

keepAliveTime:非核心线程数(线程池总数-核心线程数)保留时间

TimeUnit:保留时间单位

BlockingQueue:队列,提交优先级第二位

  主要有以下几种:

  new SynchronousQueue<>() 同步队列,如果超过maximumPoolSize则直接执行拒绝策略。

  new ArrayBlockingQueue<int capacity>:有界任务队列,如果capacity大于maximumPoolSize那么会执行完ArrayBlockingQueue的线程直接拒绝;如果ArrayBlockingQueue(int capacity)的capacity小于maximumPoolSize,那么就会执行完maximumPoolSize然后拒绝。一般采用这个队列。

  new LinkedBlockingDeque<>:*任务队列,这种队列下maximumPoolSize是无效的,拒绝策略也是无效的

  new PriorityBlockingDeque<>:优先任务队列

ThreadFactory

RejectedExecutionHandler:拒绝策略

  new ThreadPoolExecutor.AbortPolicy():默认策略,直接丢弃任务,抛出异常

  new ThreadPoolExecutor.CallerRunsPolicy():用调用者的线程去执行任务

  new ThreadPoolExecutor.DiscardOldestPolicy():丢弃掉队列中存在时间最久的任务,不会抛出异常

  new ThreadPoolExecutor.DiscardPolicy:抛弃当前任务,不会抛出异常。

 

相关参数的设置:

 

* 需要根据几个值来决定
   - 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 = 80。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
  * 切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
  - maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
  * 计算可得 maxPoolSize = (1000-80)/10 = 92
  *(最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数
  - rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
  - keepAliveTime和allowCoreThreadTimeout采用默认通常能满足

 

二、ThreadPoolExecutor执行顺序
线程池按以下行为执行任务
1. 当线程数小于核心线程数时,创建线程。 
2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3. 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,抛出异常,拒绝任务

上一篇:java线程池实践基础~!


下一篇:ES6学习笔记01 -- 暂时性死区 ( temporal dead zone )