0%

线程池-基本概念

一、常见线程池

1.Executors.newFixedThreadPool()

1
2
3
4
创建一个定长的线程池,可控制线程最大并发数,超出的线程会在队列中等待
创建线程池corePoolSize和maximumPoolSize的值是相等的,它使用的队列是LinkedBlockingQueue
构造方法
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());

2.Executors.newSingleThreadExecutor()

1
2
3
4
创建一个单线程化的线程池,他只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行
创建线程池corePoolSize和maximumPoolSize的值为1,它使用的队列是LinkedBlockingQueue
构造
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));

3.Executors.newCachedThreadPool()

1
2
3
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程corePoolSize的值为0,maximumPoolSize的值为Integer,MAX_VALUE,使用的队列是SynchronousQueue。有任务时就创建线程运行,当线程空闲60s就销毁线程
构造
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());

二、线程池底层调用

1
2
3
4
5
6
7
8
9
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue)
--> 调用自身七参构造方法public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
int corePoolSize:线程池中常驻核心线程数
int maximumPoolSize:线程池能够容纳同时执行你的最大线程数,此值必须大于等于1
long keepAliveTime:多余的空闲线程存活时间,当前线程池超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止
TimeUnit unit:keepAliveTime的单位
BlockingQueue<Runnable> workQueue:任务队列,被提交但尚未被执行的任务
ThreadFactory threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认即可
RejectedExecutionHandler handler:拒绝策略,表示当队列满了并且工作线程大于线程池的最大线程数(maximumPoolSize)时如何来拒绝

三、线程池底层原理

1
2
3
4
5
6
7
8
9
10
1)在创建了线程池后,等待提交过来的任务请求
2)当调用execute()方法添加一个请求任务时,线程会做如下判断
2.1)如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务
2.2)如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
2.3)如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务
2.4)如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行
3)当一个线程完成任务时,它会从队列中取下一个任务来执行
4)当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:
4.1) 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉
4.2)所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小

四、线程池拒绝策略

1
2
3
4
5
6
7
AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行

CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低任务流量

DiscardOldestPolicy:抛弃任务中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务

DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案

五、自定义创建线程

1
2
3
4
5
6
7
8
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());

六、注意点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
为什么不能使用常见的这三个线程池?
因为newFixedThreadPool和newSingleThreadExecutor允许创建的队列长度Integer.MAX_VALUE,长度为21亿,可能会堆积大量请求,容易造成OOM;
而newCachedThreadPool 和 newScheduledThreadPool允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量的线程,容易造成OOM

如何设置线程数?
CPU密集型,该任务需要大量的运算,而没有阻塞,CPU一直全速运行,尽可能少的线程数量,CPU核数+1个线程(经验值,用于补充中间可能涉及的IO耗时)。
IO密集型 与CPU密集型相对,一个完整请求,CPU运算操作完成之后还有很多IO操作,IO操作占比很多大,等待时间较长;线程等待时间所占比例越高,需要的线程越多;
由于IO密集型任务线程并不是一直在执行任务,则应该配置尽可能多的线程,
1.常见公式,如果几乎都是IO耗时,那么CPU耗时无限趋近于0:CPU核数*2或者CPU核数*2+1经(验值,用于补充中间可能涉及的IO耗时)
2. 最佳线程数=CPU核心数*(1/CPU利用率)=CPU核心数*(1+(IO耗时/CPU耗时))

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的人更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool :
允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
2) CachedThreadPool 和 ScheduledThreadPool :
允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。