volatile
volatile是Java虚拟机提供的轻量级的同步机制,保证被volatile修饰的共享变量的值,新值对总是可以被其他线程立即得知。禁止指令重排序优化,但volatile并不能保证原子性。
- volatile禁止指令重排序是通过内存屏障实现的。
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoad | Load1; LoadLoad; Load2 | 保证load1的读取操作在load2及后续读取操作之前执行 |
StoreStore | Store1; StoreStore; Store2 | 在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存 |
LoadStore | Load1; LoadStore; Store2 | 在stroe2及其后的写操作执行前,保证load1的读操作已读取结束 |
StoreLoad | Store1; StoreLoad; Load2 | 保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行 |
volatile内存语义的实现
第一个操作 | 第二个操作:普通读写 | 第二个操作:volatile读 | 第二个操作volatile写 |
---|---|---|---|
普通读写 | 可以重排 | 可以重排 | 不可以重排 |
volatile读 | 不可以重排 | 不可以重排 | 不可以重排 |
volatile写 | 可以重排 | 不可以重排 | 不可以重排 |
基于保守策略的JMM内存屏障插入策略
- 在每个volatile写操作前插入一个StoreStore屏障
- 在每个volatile写操作后插入一个StoreLoad屏障
- 在每个volatile读操作后插入一个LoadLoad屏障
- 在每个volatile读操作后插入一个LoadStore屏障
Java内存模型
Java内存模型是一种抽象的概念,它描述的是一组规则或规范,通过这组织规范定义了程序中各个变量的访问方式。
数据同步的八大原子操作
- lock(锁定):作用于主内存中的变量,把一个变量标记为一条线程独占状态。
- unlock(解锁):作用于主内存中的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load(载入):作用于工作内存的变量,将read操作从主内存中得到的变量值放入工作内存的变量副本中
- use(使用):作用于工作内存的变量,将工作内存中的一个变量值传递给执行引擎
- assign(赋值):作用于工作内存的变量,将一个从执行引擎接收到的值赋给工作内存的变量。
- store(存储):作用于工作内存的变量,把工作内存中一个变量的值传送到主内存的变量中。
- write(写入):作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中。
JMM如何解决原子性&可见性&有序性问题
- 原子性:除了JVM自身提供的对基本数据类型读写操作的原子性外,可以通过synchronize和lock实现原子性。因为synchronize和Lock能够保证任一时刻只有一个线程访问该代码块。
- 可见性:volatile关键字保证可见性。当一个共享变量被volatile 修饰时,它会保证修改的值立即被其他的线程看到,即修改的值立即被其他的线程看到,修改的值立即更新到主内存中,当其他线程需要读取时,它会去内存中读取新值。
synchronized和Lock也可以保证可见性,因为它们可以保证任一时刻只有一个线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中。 - 有序性:可以通过volatile关键字禁止指令的重排序实现有序性。synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。