Volatile 详解

Posted by Young Ken on 2016-12-15

Volatile详解

在java语言中有两种同步机制,分别是同步块和Volatile。这两个方式都是为了保证线程安全的,但是其中的Volatile的同步性能差。

Java中Volatile变量可以看做是一种轻量级的synchronized;同synchronized对比,Volatile使用更加简单,运行开销更加小,它的功能只是synchronized的一部分。

锁主要有两个特性:互斥(mutual exclusion) 和可见性(visibility)。互斥性指的是一个线程只能持有某个特定的锁,这样一次只能有一个线程访问共享数据。可见性,数据任何时候都对其他线程可见。

Volatile变量

Volatile变量具有可见性,但是不具备原子性。
保证正确使用Volatile必须同时满足两个条件

  • 对变量的写入不依赖当前值。
  • 该变量没有包含在具有其他变量的不变式中。

不能拿Volatile当做计数器使用,因为第一个条件决定了,计数器是读取-修改-写入的过程,每次++都依赖上次的值。
这个例子也不能适用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@NotThreadSafe
public class NumberRange {
private int lower, upper;
public int getLower() { return lower; }
public int getUpper() { return upper; }
public void setLower(int value) {
if (value > upper)
throw new IllegalArgumentException(...);
lower = value;
}
public void setUpper(int value) {
if (value < lower)
throw new IllegalArgumentException(...);
upper = value;
}
}

当我们初始化值是(0,5),我们同时访问setLower(4),setUpper(3)这个时候值变成了(4,3)显然这是个错误的值。

正确使用volatile 的模式

状态标志

下面这个例子将Volatile作为标志位:

1
2
3
4
5
6
7
8
9
10
11
volatile boolean shutdownRequested;
...
public void shutdown() { shutdownRequested = true; }
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}

这种类型状态标记一个公共的特征:通常用于一种状态转换;shutdownRequested 标志从 false 转换为 true,然后程序停止。

一次性安全发布

缺乏同步无法实现可见性。在缺乏同步的情况下,可能会遇到某个对象引用的更新值(由另一个程序写入)和该对象状态的旧值同时存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BackgroundFloobleLoader {
public volatile Flooble theFlooble;
public void initInBackground() {
// do lots of stuff
theFlooble = new Flooble(); // this is the only write to theFlooble
}
}
public class SomeOtherClass {
public void doWork() {
while (true) {
// do some stuff...
// use the Flooble, but only if it is ready
if (floobleLoader.theFlooble != null)
doSomething(floobleLoader.theFlooble);
}
}
}

该模式的一个必要条件是:被发布的对象必须是线程安全的,或者是有效的不可变对象(有效不可变意味着对象的状态在发布之后永远不会被修改)。

“volatile bean” 模式

Volatile bean 模式适用于将 JavaBeans 作为“荣誉结构”使用的框架。
在Volatile bean中每个get set必须不能包含其他逻辑,此外,对于对象引用的数据成员,引用的对象必须是有效不可变的。这将禁止具有数组值的属性,因为当数组引用被声明为 volatile 时,只有引用而不是数组本身具有 volatile 语义)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@ThreadSafe
public class Person {
private volatile String firstName;
private volatile String lastName;
private volatile int age;
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public int getAge() { return age; }
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setAge(int age) {
this.age = age;
}
}