0%

Volatile的使用

Volatile 介绍

在多线程并发编程中 synchronized 和 volatile 都扮演着重要的角色,volatile 是轻量级的 synchronized, 它在多处理器开发中保证了共享变量的“可见性”。 可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当的话,它比 synchronized 的使用和执行成本更低,因为它不会引起线程上下文的切换和调度。

Java内存模型

其中本地内存并不真实存在,是一个抽象概念,涵盖了缓存,写缓冲区,寄存器以及其他硬件和编译器优化.

Volatile的特性

  • 可见性:对 一个 volatile 变量 的 读, 总是 能看 到( 任意 线程) 对 这个 volatile 变量 最后 的 写入。

  • 原子性:对 任意 单个 volatile 变量 的 读/ 写 具有 原子 性, 但 类似于 volatile++ 这种 复合 操作 不具 有 原子 性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* volatile
*/
class VolatileFeaturesExample {
volatile long vl = 0L; // 使用 volatile 声明 64 位 的 long 型 变量
public void set( long l) {
vl = l; // 单个 volatile 变量 的 写
}
public void getAndIncrement () {
vl++; // 复合( 多个) volatile 变量 的 读/ 写
}
public long get() {
return vl; // 单个 volatile 变量 的 读
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class VolatileFeaturesExample {
long vl = 0L; // 64 位 的 long 型 普通 变量
public synchronized void set( long l) {
// 对 单个 的 普通 变量 的 写 用 同一个 锁 同步
vl = l;
}
public void getAndIncrement () {
// 普通 方法 调用
long temp = get(); // 调用 已 同步 的 读 方法
temp += 1L; // 普通 写 操作
set( temp); // 调用 已 同步 的 写 方法
}
public synchronized long get() {
// 对 单个 的 普通 变量 的 读 用 同一个 锁 同步
return vl;
}
}

以上两种代码等价.锁的 happens- before 规则保证释放锁和获取锁的两个 线程之间的内存可见性,这意味着对一个volatile变量的读,总是能看到( 任意 线程)对这个volatile变量最后的写入。

Volatile 内存语义

写: 当 写 一个 volatile 变量 时, JMM 会把 该 线程 对应 的 本地 内存 中的 共享 变量 值 刷 新到 主 内存。

读: 当 读 一个 volatile 变量 时, JMM 会把 该 线程 对应 的 本地 内存 置 为 无效。 线程 接下来 将从 主 内存 中 读取 共享 变量。

Volatile 内存语义的实现

JMM为实现Volatile内存语义,会对编译器和处理器重排序进行限制,限制如下:

  • Volatile写之前的操作必然发生在volatile写之前
  • Volatile读之后的操作必然发生在volatile读之后
  • Volatile读之前的Volatile写必然发生在Volatile读之前

内存屏障类型:

实现:编译器 在 生成 字节 码 时, 会在 指令 序列 中 插入 内存 屏障 来 禁止 特定 类型 的 处理 器重 排序。

  • 在每个Volatile写之前插入StoreStore屏障
  • 在每个Volatile写之后插入StoreLoad屏障
  • 在每个Volatile读之前插入LoadLoad屏障
  • 在每个Volatile读之后插入LoadStore屏障
您的支持是对我最大的动力 (●'◡'●)