1 volatile的基本信息
1.1 volatile的特性
可见性、有序性(禁止重排 )。
volatile不具备原子性。在一个线程(t1)操作volatile变量时,另一个线程(t2)更新了该volatile变量的值,则t1线程需要去主内存中获取最新的volatile变量值,t1线程之前的操作则全部作废,出现操作丢失问题。由此可见volatile解决的是可见性的问题,而无法保证其原子性。所以在多线程修改主内存变量的情况下必须加锁。
1.2 volatile变量的读写过程
- 在对一个volatile变量进行写操作时,JMM会将该线程工作内存中的该volatile变量立即刷新回主内存中,并通知其它线程该变量已更新。
- 在对一个volatile变量进行读操作时,JMM会将该线程工作内存中的该volatile变量置为无效,重新回到主内存中读取该变量。
1.2.1 8个原子性操作
- read(读取):作用于主内存,将变量从主内存读取到工作内存中。
- load(加载):作用于工作内存,将读取到工作内存中的变量放入工作内存的变量副本中(工作线程修改的都是自己副本中的变量)。
- use(使用):作用于工作内存,将工作内存中的变量副本中的值传递给执行引擎进行操作。
- assign(赋值):作用于工作内存,将执行引擎中返回的值赋值给工作内存中的变量副本。
- store(存储):作用于工作内存,将赋值完毕的变量副本写回给主内存。
- write(写入):作用于主内存,将工作内存写回的值赋值给主内存中的变量。
- lock(锁定):作用于主内存,在工作内存写回变量的过程中,为防止多个线程同时向主内存写入,因此需要加锁,确保同一时刻只能有一个线程写入。
- unlock(解锁):作用于主内存,对应lock,线程写入后解锁,其他线程才能继续写入该变量。
1.2.2 volatile变量的读写过程流程图
从volatile的读写过程中,可以看出改变volatile变量后通知其它线程实现了volatile的可见性。
1.3 volatile的适用场景
- 状态标志:将volatile修饰的状态变量改变时,会立刻通知其他线程。
- 开销较低的读,写锁策略:当读远多于写的情况下,可以将变量设置为volatile,则只需要在写操作上添加sync同步即可,每次写操作都会通知读操作去读取最新的值。
- 单例模式中为变量添加volatile,保证多线程情况下new对象时禁止指令重排的问题(先获取一块内存地址,再通过这块内存初始化对象,再返回对象的地址);
2 内存屏障
2.1 内存屏障的作用
内存屏障是为了保证volatile的有序性,即禁止指令重排序(在多线程环境下,指令会根据编译器等重排序)的一种屏障指令。主要用于禁止屏障两侧的指令重排。
2.2 内存屏障的实现方式
当一个变量被定义为volatile时,在其字节码层面会为这个字段的flags里添加一个ACC_VOLATILE,jvm在生成对应的字节码指令时,发现操作的是volatile变量时,会在相应位置插入内存屏障。
2.3 内存屏障分类
通过读屏障与写屏障之间的配合,能够实现指令的顺序执行,从而达到有序性的特点。
2.3.1 写屏障
告诉处理器,在写屏障之前的所有存储在工作内存中的变量数据同步到主内存中。即在写屏障之前必须将所有写入指令执行完成才能继续执行。
在写指令之前插入写屏障,将写屏障之前的缓存数据强制刷回主内存中。
2.3.2 读屏障
处理器在读屏障之后的操作只能在读屏障之后进行。即保证读屏障之后的读操作一定能读取到最新的数据。
在读指令之前插入读屏障,让工作内存或cpu中的缓存数据失效,从而回到主内存中读取最新的数据。
2.3.3 四种屏障
- LoadLoad屏障(Load1;LoadLoad;Load2):保证Load1的读取在Load2之前执行。
- StoreStore屏障(Store1;StoreStore;Store2):保证在Store2写操作之前,Store1的写操作已经刷新到主内存。
- LoadStore屏障(Load1;LoadStore;Store1):保证在Store1写操作之前,Load1的读操作已经完成。
- StoreLoad屏障(Store1;StoreLoad;Load1):保证在Load1读操作之前,Store1以及之前的写操作已经刷新回主内存中。
2.4 volatile规则之四种屏障的位置
- 当第一个操作为volatile读操作时,无论第二个是什么操作,都不能重排;
- 当第二个操作为volatile写操作时,无论第一个操作是什么,都不能重排;
- 当第一个操作为volatile写操作,第二个为volatile读操作时,不能重排;
2.4.1读操作内存屏障指令示意图
2.4.2 写操作内存屏障指令示意图
3 总结
通过上述内存大致可以看出,volatile的读写流程实现了其可见性,即读写完立即通知其他线程获取最新的值。volatile的内存屏障则实现了其有序性(禁止重排),通过四种读写屏障强制指定读写命令的顺序。