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. 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,抛出异常,拒绝任务