多线程与高并发编程(八)【线程池二】
多线程与高并发编程(八)【线程池二】
一、线程池
1. SignleThreadPool
单线程的线程池。为什么要有单线程的线程池:需要维护一个任务队列、生命周期管理。
Executors.newSingleThreadExecutors();
Executors 线程池的工厂(就像Arrays是list的工厂,Collections是容器的工厂)
阿里开发手册要求不要使用JDK自带的实现来定义线程池,不推荐这个,因为LinkedBlockingQueue队列是有上界的,integer.max,越积越多最后会OOM;到了上界之后JDK默认的拒绝策略在生产环境可能会出问题;线程名称的问题,所以需要自定义。
源码:
2. CachedThreadPool
来一个任务直接就进入线程池中找线程,若没有空闲线程则new一个线程在池中处理,上限是integer.max,因为队列是synchronusQueue所以容量为0,所以来了任务必须直接处理。
Executors.newCachedThreadPool();
阿里中不推荐原因是可能会起非常多的线程。
源码:
3. FixedThreadPool
指定参数,一共有多少个线程
阿里中不推荐原因是同singleThreadPool
源码:
Cached VS Fixed:
可估算、线程数比较平稳的用fixed。
阿里都不用,自己估算,进行精确定义
4. ScheduledThreadPool
定时任务线程池,这个队列可以让任务每搁多长时间运行。
时间可以用quartz框架、cron框架(复杂的时间框架,不复杂可以用Timer)。
.scheduleAtFixedRate(callable, initialDelay, period, TimeUnit) 【initialDelay:第一个任务执行之前需要等多长时间。period:每隔多长时间。最后一个单位】
源码:
面试题(开发题没标准答案):假如提供一个闹钟服务,订阅这个服务的人特别多,十亿人,怎么优化?
答:比如分发任务到很多边缘服务器,每个服务器上用线程池和队列。
并行与并发的区别:
并发:concurrent 指任务提交,从人的角度看多个任务涌过去是并发,实际上可能是一个cpu不同的顺序处理。
并行:parallel 指任务执行,真正意义上多个cpu同时处理。
并行是并发的子集
总结:以上四种线程池底层实现都是利用ThreadPoolExecutor
手写拒绝策略:
自定义个类实现RejectedExecutionHandler,重写方法内写入想要的操作。
下图自定义思路:
log生成日志,save保存日志到哪,尝试n次放回池子,如果队列数量小于n(自定义的队列数量),则再试一下放入。
5. WorkStealingPool
每个线程都在池内有一个自己的任务队列,当队列中空了之后去别的线程的队列那去偷一个拿来执行。
源码:
实际上是利用ForkJoinPool,将大人物进行拆分处理最后汇总(比如1亿个数相加)。
因为任务需要可以拆分,所以与其他的池通过runnable不同,有两种实现:
①需要继承RecursiveAction类(没有返回值)
compute之中定义AddTask(开始位置,结束位置)调用.fork()执行分叉
主方法执行:
②继承自RecursiveTask
compute方法(这次的类叫AddTaskRet):
主方法:
6. ParallelSteamAPI
底层也是利用ForkJoinPool实现。
下图利用Lambda表达式,T13_ParallerlSteamAPI为当前类。
isPrime()是一个自定义的方法,里面是自己写的判断是否为质数的逻辑。
二、ThreadPoolExecutor源码观后感
总结流程:
1. worker类:
本身是一个Runnable也是一个AQS
Runnable是为了记录一些原本没有的东西。
AQS是因为worker会被多线程访问,本身直接就做成了一把锁。
成员变量有个Thread记录哪个线程抢到了。
2. submit方法
3. execute方法:
核心线程数不到最大核心数则起一个核心线程处理。
核心线程数最大了则放入队列。
上面两个都满了,起非核心线程处理。
4. addWorker方法:
干了两件事:
①count++ 线程数量先加1
②真正加了一个线程处理并启动