java的线程是一种高成本的实现方案,在hotspot jvm的实现中,任意的起一个新线程 (Java.lang.thread)都会一对一的映射到本地的操作系统线程上,这样做的好处是本地操作系统的线程非常的安全,以及健壮性可以信任。
但是这也带来了性能的巨大问题,如果每次创建异步任务都创建新的线程,很容易就会导致oom以及性能的问题。在go以及一些新的语言中,他们特意引入了新的任务承载方案:协程,协程会在同一个线程上复用多个任务以达到轻量级的实现的方案。当然在我们现在广为使用的1.8jdk中并没有对此有实现的方案,java改进效率实现的方案是线程池。
线程池
线程池是可以进行复用的一种工具类
方法:当需要使用线程时,从池中取一个空闲线程,用毕归还即可
优势: 减少系统消耗 能避免频繁创建和销毁线程 数据库连接池也是类似的设计思路
Executor 框架
线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了。 Executor 框架 大概分为如下3大部分“
- 任务:包括被执行任务需要实现的接口:Runnable接口或者Callable接口
- 任务的执行 包括任务执行机制的核心接口Executor,以及继承自Executor的 ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口 (ThreadPoolExecutor和ScheduledThreadPoolExecutor)。
- 异步计算的结果。包括接口Future和实现Future接口的FutureTask类
java.uitl.concurrent.ThreadPoolExecutor 类是线程池的核心 如果讨论线程池必须要先讨论这个类的代码
固定线程池:
通过newFixedThreadPool创建 固定线程数量 有空闲线程,则立刻处理新的任务 无空闲线程,则暂存在任务队列中 single
可变线程池
newCachedThreadPool 可以调整线程数量的线程池 优先复用空闲的线程 可以创建线程去处理任务
可被调度计划任务
创建可被调度的线程池 newScheduledThreadPool 返回ScheduledExcutorService 给定时间或延时后执行任务,schedule (command ,delay,unit) 周期性执行任务, scheduleAtFixRate
ThreadPoolExecutor 线程池构造方法“
ThreadPoolExecutor 任务调度策略 :
工作队列 WorkQueue 拒绝策略 任务数量超过系统实际承载能力
- AbortPolicy 直接抛出异常阻止
- CallerRunsPolicy 线程中运行当前被丢弃的任务
- DiscardOldestPolicy 丢弃最老的请求
- DiscardPolicy 丢弃无法处理的任务
线程数量的估算方式:
和 CPU 核数有数量关系
- 计算密集型:线程数等于 CPU 核数
- I/O 密集型:等待时间过多,正相关 Nthread = Ncpu * Ucpu * (1+ W/C)
- Nthread:线程数量
- Ncpu:CPU 的数量
- Ucpu:CPU 使用率,范围在 [0,1]
- W/C:等待时间与计算时间的比率
Fork /Join框架
- Fork创建子任务,join等待任务完成
- 委托给ForkJoinPool线程池
- 已经完成的线程,可以从任务队列底部拿数据来帮助别的线程
- ForkJoinTask 提交的任务
-
recursiveAction 无返回值的任务 -
recursiveTask有返回值的任务
-
fork/join大体的执行过程就如上图所示,先把一个大任务分解(fork)成许多个独立的小任务,然后起多线程并行去处理这些小任务。处理完得到结果后再进行合并(join)就得到我们的最终结果。
StampedLock
原理: 乐观读读的时候不加锁,除非发现数据被修改 通过对比版本号的方式,废弃之前读出来的数据 再变为悲观读,加锁操作 参考mvcc机制,一份数据有多个版本 提升了读写效率