java多线程(5)

102 阅读3分钟

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 任务调度策略 :

屏幕快照 2021-12-29 下午9.53.01.png

工作队列 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有返回值的任务
      

屏幕快照 2021-12-26 下午9.12.41.png

fork/join大体的执行过程就如上图所示,先把一个大任务分解(fork)成许多个独立的小任务,然后起多线程并行去处理这些小任务。处理完得到结果后再进行合并(join)就得到我们的最终结果。

StampedLock

原理: 乐观读读的时候不加锁,除非发现数据被修改 通过对比版本号的方式,废弃之前读出来的数据 再变为悲观读,加锁操作 参考mvcc机制,一份数据有多个版本 提升了读写效率