Java并发编程艺术 - volatile使用和原理

Java代码经过Java编译器编译成字节码,并由类加载器加载到JVM运行时数据区,
最终由JVM执行引擎执行字节码,转换为汇编指令在CPU内执行。

Java并发机制主要依赖JVM实现和CPU指令。

volatile

volatile修饰变量,提供多处理器并发中共享变量的可见性,是轻量的synchronized。

volatile由CPU指令实现,不会引起上下文切换,性能比synchronized高。

可见性问题

可见性问题是受Java内存模型的影响。

WX20190505-111203@2x.png

从上图可知,多线程对共享变量在本地内存进行处理,虽然提高执行效率,但是将带来可见性和数据同步问题。

多线程编程中,对于volatile共享变量,一个线程进行了修改,对于其他线程能及时读取修改后的值,以做到资源同步。

volatile定义

如果一个变量被声明为volatile,那么该变量在多线程中将互相可见,保障准确和一致性。

内存屏障/内存栅栏

一系列CPU指令,控制内存操作的顺序,可以解决指令重排和可见性问题。

其中,指令重排是JIT对代码的优化。

内存屏障插入策略:

  • 在volatile写前插入storestore,禁止上面普通写与volatile写重排
  • 在volatile写后插入storeload,禁止下面volatile读/写与volatile写重排
  • 在volatile读后插入loadload,禁止下面普通读与volatile写读重排
  • 在volatile读后插入loadstore,禁止下面普通写与volatile读重排

volatile实现原理

在共享变量写前,CPU新增Lock操作指令,实现以下效果:

  • 将当期缓存行回写系统内存
  • 其他CPU缓存该内存地址数据无效

为提高处理速度,CPU不和主存直接交互,而是将主存数据读取到缓存,但缓存写回内存时间未知。

通过volatile将通过Lock指令实现写回操作,其他处理器通过总线检查缓存值是否过期;若发现
缓存行地址变更,则将缓存行置为无效。

当处理器处理数据时,发现无效标志则会从主存中重新加载数据至缓存。

说明

  • Lock前缀指令会引起处理器缓存写回内存。Lock可以锁定缓存区域,实现原子操作。
  • 处理器缓存写回操作将引起其他处理的该缓存无效。处理器嗅探缓存地址,若处于共享状态,无效缓存行后,下次访问进行强制缓存行填充。

volatile优化

  • JDK7追加64字节能够提高LinkedTransferQueue并发编程的效率

    缓存的最小单位是缓存行,常见CPU的缓存行是64字节宽(8字节),不支持部分缓存行。

    不满64字节,则缓存队列头和尾至同一缓存行。

    当CPU修改头节点,需要锁定缓存行,导致其他CPU不能处理缓存,影响队列入队和出队效率。

    追加至64位,填满缓存行,避免队列的头和尾节点被同一缓存行同时锁定。

volatile使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class VolatileTest {

//原子变量,缓存无效直接取主存
private volatile int num = 0;

//检测值变化
private void test() {
while (true) {
//测试在一行代码中,num是否会有多个值情况
if (num == 1 && num == 2 && num == 3) {
System.out.println("不一致");
}
}
}

//修改值
private void change() {
while (true) {
//频繁修改num值
for (int i = 0; i < 4; i++) {
num = i;
}
}
}

public void start() {
new Thread(this::test).start();
new Thread(this::change).start();
}

public static void main(String[] args) {
new VolatileTest().start();
}
}

结果频繁出现:不一致
若不使用volatile,因为线程执行速度快,偶尔会出现不一致但不频繁
说明volatile可以实时同步各线程共享变量

volatile修饰用法

  • volatile 基本变量int\boolean等,例如volatile int a=1;保障可见性
  • volatile 对象,例如volatile Singleton singleton;防止指令重排

注意

  • volatile保障变量在线程间的可见性,但不保障操作原子性
------ 本文结束------

本文标题:Java并发编程艺术 - volatile使用和原理

文章作者:Perkins

发布时间:2019年05月05日

原始链接:https://perkins4j2.github.io/posts/34733/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。