[size=xx-large]概述[/size]
您只能在有限的一些情形下使用 volatile 变量替代锁。要使volatile变量提供理想的线程安全,必须同时满足下面两个条件:
[list]
[*]对变量的写操作不依赖于当前值。
[*]该变量没有包含在具有其他变量的不变式中。
[/list]
实际上,这些条件表明,可以被写入volatile变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
与锁相比,Volatile 变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性。如果严格遵循 volatile 的使用条件
—— 即变量真正独立于其他变量和自己以前的值
—— 在某些情况下可以使用 volatile 代替 synchronized 来简化代码。
然而,使用 volatile 的代码往往比使用锁的代码更加容易出错。当你在线程中读取一个非volatile变量时,你最终能读取它更新的那个值。
[size=xx-large]原子访问[/size]
在编程过程中,原子操作是指所有操作都同时发生。原子操作不能被中途打断:要么全做,要么不做。原子操作在完成前不会有看得见的副作用。
我们发现像c++这样的增量表达式,并没有描述原子操作。即使是非常简单的表达式也能够定义成能被分解为其他操作的复杂操作。然而,有些操作你可以定义为原子的:
对引用变量和大部分基本类型变量(除long和double之外)的读写是原子的。
对所有声明为volatile的变量(包括long和double变量)的读写是原子的。
原子操作不会交错,于是可以放心使用,不必担心线程干扰。然而,这并不能完全消除原子操作上的同步,因为内存一致性错误仍可能发生。使用volatile变量可以降低内存一致性错误的风险,因为对volatile变量的任意写操作,对于后续在该变量上的读操作建立了happens-before关系。这意味着volatile变量的修改对于其他线程总是可见的。更重要的是,这同时也意味着当一个线程读取一个volatile变量时,它不仅能看到该变量最新的修改,而且也能看到致使该改变发生的代码的副效应。
使用简单的原子变量访问比通过同步代码来访问更高效,但是需要程序员更加谨慎以避免内存一致性错误。至于这额外的付出是否值得,得看应用的大小和复杂度。
java.util.concurrent包中的一些类提供了一些不依赖同步机制的原子方法。
[size=xx-large]常规用法[/size]
[b]模式 #1:状态标志[/b]
[b]模式 #2:一次性安全发布(one-time safe publication)[/b]
[b]模式 #3:独立观察(independent observation)[/b]
[b]模式 #4:“volatile bean” 模式[/b]
[b]模式 #5:开销较低的读-写锁策略[/b]
您只能在有限的一些情形下使用 volatile 变量替代锁。要使volatile变量提供理想的线程安全,必须同时满足下面两个条件:
[list]
[*]对变量的写操作不依赖于当前值。
[*]该变量没有包含在具有其他变量的不变式中。
[/list]
实际上,这些条件表明,可以被写入volatile变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
与锁相比,Volatile 变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性。如果严格遵循 volatile 的使用条件
—— 即变量真正独立于其他变量和自己以前的值
—— 在某些情况下可以使用 volatile 代替 synchronized 来简化代码。
然而,使用 volatile 的代码往往比使用锁的代码更加容易出错。当你在线程中读取一个非volatile变量时,你最终能读取它更新的那个值。
[size=xx-large]原子访问[/size]
在编程过程中,原子操作是指所有操作都同时发生。原子操作不能被中途打断:要么全做,要么不做。原子操作在完成前不会有看得见的副作用。
我们发现像c++这样的增量表达式,并没有描述原子操作。即使是非常简单的表达式也能够定义成能被分解为其他操作的复杂操作。然而,有些操作你可以定义为原子的:
对引用变量和大部分基本类型变量(除long和double之外)的读写是原子的。
对所有声明为volatile的变量(包括long和double变量)的读写是原子的。
原子操作不会交错,于是可以放心使用,不必担心线程干扰。然而,这并不能完全消除原子操作上的同步,因为内存一致性错误仍可能发生。使用volatile变量可以降低内存一致性错误的风险,因为对volatile变量的任意写操作,对于后续在该变量上的读操作建立了happens-before关系。这意味着volatile变量的修改对于其他线程总是可见的。更重要的是,这同时也意味着当一个线程读取一个volatile变量时,它不仅能看到该变量最新的修改,而且也能看到致使该改变发生的代码的副效应。
使用简单的原子变量访问比通过同步代码来访问更高效,但是需要程序员更加谨慎以避免内存一致性错误。至于这额外的付出是否值得,得看应用的大小和复杂度。
java.util.concurrent包中的一些类提供了一些不依赖同步机制的原子方法。
[size=xx-large]常规用法[/size]
[b]模式 #1:状态标志[/b]
volatile boolean shutdownRequested;
...
public void shutdown() { shutdownRequested = true; }
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}
[b]模式 #2:一次性安全发布(one-time safe publication)[/b]
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);
}
}
}
[b]模式 #3:独立观察(independent observation)[/b]
public class UserManager {
public volatile String lastUser;
public boolean authenticate(String user, String password) {
boolean valid = passwordIsValid(user, password);
if (valid) {
User u = new User();
activeUsers.add(u);
lastUser = user;
}
return valid;
}
}
[b]模式 #4:“volatile bean” 模式[/b]
@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;
}
}
[b]模式 #5:开销较低的读-写锁策略[/b]
@ThreadSafe
public class CheesyCounter {
// Employs the cheap read-write lock trick
// All mutative operations MUST be done with the 'this' lock held
@GuardedBy("this") private volatile int value;
public int getValue() { return value; }
public synchronized int increment() {
return value++;
}
}