JMM内存模型
背景
CPU架构
一、进程是计算机分配资源的基本单位,线程是CPU调度的基本单位。
同一个进程内的所有线程共享堆内存;
栈内存的每个线程自己持有的,不能共享,这里的栈内存就是JAVA中的工作内存。
为了提高计算机运算速度:
1、加入了高速缓冲
2、加入了指令重排序
①编译器重排序
②流水线并行重排序
③写缓冲队列,无效队列重排序
- 写缓冲区(Store Buffer)
由于在写入操作之前,CPU 核心 1 需要先广播 RFO 请求获得独占权,在其它核心回应 ACK 之前,当前核心只能空等待,这对 CPU 资源是一种浪费。因此,现代 CPU 会采用 “写缓冲区” 机制:写入指令放到写缓冲区后并发送 RFO 请求后,CPU 就可以去执行其它任务,等收到 ACK 后再将写入操作写到 Cache 上。
- 失效队列(Invalidation Queue)
由于其他核心在收到 RFO 请求时,需要及时回应 ACK。但如果核心很忙不能及时回复,就会造成发送 RFO 请求的核心在等待 ACK。因此,现代 CPU 会采用 “失效队列” 机制:先把其它核心发过来的 RFO 请求放到失效队列,然后直接返回 ACK,等当前核心处理完任务后再去处理失效队列中的失效请求。
带来的问题:
可见性、有序性、原子性
二、JVM的并发机制
1、volatile关键字:
volatile比synchronized执行成本更低,因为他不会引起线程上下文的切换和调度
被volatile修饰的共享变量进行写操作的时候会多出一条以lock为前缀的汇编指令:1、lock指令会引起会引起处理器缓冲回写到内存 2、一个处理器的缓冲回写到内存会引起其它处理器的缓冲失效
volatile关键字的作用:1、保证可见性 2、禁止指令重排序【但这两个功能的底层实现都是lock指令】
volatile只能保证可见性不能保证原子性
2、synchronized关键字(抛出异常自动释放锁)
所以synchronized的底层操作含义是先对对象头的锁标志位用lock cmpxchg的方式设置成“锁住“状态,释放锁时,在用lock cmpxchg的方式修改对象头的锁标志位为”释放“状态,写操作都立刻写回主内存
无所状态---->偏向锁状态---->撤销偏向锁状态---->轻量级锁状态---->重量级锁状态
偏向锁的撤销是狠耗时的
偏向锁通过偏向锁的撤销膨胀成轻量级锁·
轻量级锁通过自旋膨胀成重量级锁
3、原子操作的实现原理:(CAS操作同时具有volatile读-写的语义)
(1)、总线锁
(2)、缓冲锁(并不是都支持)
(3)、CAS就是利用lock CMPXCHG指令实现的(JAVA所用)
①ABA问题
②循环时间长开销大
③只能保证一个共享变量的原子操作
④JVM的synchronized的机制中也用了CAS的锁机制
三、JVM的内存模型
线程之间的通信方式:共享内存(JAVA所用)和消息传递
堆内存是所有线程共享的,栈内存是线程独享的。
缓冲区和重排序这两个问题
重排序:编译器重排序、指令集并行重排序、内存系统重排序【JMM会限制编译器重排序,也会通过插入内存屏障的指令的方式来禁止一些处理器重排序】
由于处理器使用了写缓冲区,所以会对storeload操作进行重排序【这个屏障会把当前所有写缓冲区的数据全部刷新到内存中】
as-if-serial[同一线程内的数据依赖性]
happen-before[多线程之间存在可见性,那么就一定存在happen-before规则]
顺序一致性模型
synchronized及临界区内的代码是可以重排序的
1、volatile语义:
严格限制volatile变量和普通变量的重排序
volatile的单个变量的读写操作具有原子性
对一个volatile变量的读,总是能看到之前最后一次对这个变量的写
volatile的写和锁的释放具有相同的内存语义
volatile的读和锁的获取具有相同的内存语义
当写一个volatile变量时,JMM会把该线程对应的本地内存中的所有写的共享变量值刷新到主存中
当读一个volatile变量时,JMM会把该线程对应的本地内存值为无效。线程接下来将从主内从中读取所有读的共享变量
内存语义的实现:1、storestore volatile写 storeload 2、volatile读 loadload loadstore
用途:一写多读
2、锁的内存语义:
临界区互斥执行
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中
当线程获取锁时,JMM会把该线程对应的本地内存值为无效,从主内从中刷新共享变量
内存语义的实现:
CAS(compareAndSwap())同时具有volatile的写和读的内存语义
3、final的内存语义:
(1)在构造函数中对final域的写入和把这个堆对象赋值给它的引用,他们之间不能重排序
(2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能进行重排序
4、CAS的实现:
①用volatile实现可见性lock add(会锁总线或者锁缓冲行)
②用lock cmpxchg实现原子性(会锁总线或者锁缓冲行)
③用无限循环不断尝试实现原子性,保证最后成功
垃圾回收器:
1、垃圾回收线程是怎么让其他用户线程到安全点都自动停止下来的?
主动式中断:不对线程操作,仅仅设置中断标志;每个线程执行时主动轮询这个标志,发现中断标志为真时,就自己中断挂起;轮询标志的地方和安全点是重合的。
2、垃圾回收线程是怎么让其他用户线程到安全区域都自动停止下来的?
当位于安全区域的线程在线程离开安全区域时,会检测系统是否已经完成了根节点的枚举,如果没有完成,则必须等待收到离开区域的信号为止。
线程
引起线程上下文切换的原因:
上下文切换问题:时间片一般是几十毫秒,任务从保存到再加载的过程就是一次上下文切换,多线程竞争锁时会引起上下文切换,时间片到了会引起上下文切换
减少上下文切换:1、无锁并发线程 2、CAS算法 3、使用最少线程 4、协程
死锁问题:1、尽量避免一个线程同时获得多个锁
2、尽量避免一个线程在锁内同时占用多个资源
3、尝试使用定时锁lock.tryLock(timeout)
4、对于数据库锁,加锁和解锁必须在同一个数据库连接里