原文链接:
Java theory and practice: Managing volatility
要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
对变量的写操作不依赖于当前值。
该变量没有包含在具有其他变量的不等式中。
即假设volatile变量为n,那么不应有n++,n>m等用法。
volatile的错误用法1
public class VolatileTest {
public static volatile int count = 0;
public static void main(String[] args) {
// 同时启动1000个线程,去进行i++计算,看看实际结果
Thread threads[] = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
// 这里延迟1毫秒,使得结果明显
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
//increase();
}
});
threads[i].start();
}
// 等待所有线程结束
try {
for (int i = 0; i < 1000; i++) {
threads[i].join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里每次运行的值都有可能不同,可能为1000
System.out.println("运行结果:count=" + count);
}
public static synchronized void increase() {
// 进入和退出synchronized块或方法时,会同步线程的临时变量与内存主变量。保证count的值的正确性。
count++;
}
}
上述代码运行时,我们期望的结果是1000,可惜实际运行时结果并不一定是1000。
正确的做法是将count++替换成同步方法increase。
由于同步方法会自动同步线程的缓存与主内存中的变量,所以此时count变量无需设为volatile。
volatile的错误用法2
public class VolatileTest2 {
private volatile int lower = 0;
private volatile int upper = 10;
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;
}
public void test() {
new Thread(new Runnable() {
@Override
public void run() {
setLower(5);
System.out.println("lower:" + lower + ",upper:" + upper);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
setUpper(3);
System.out.println("lower:" + lower + ",upper:" + upper);
}
}).start();
}
public static void main(String[] args) {
new VolatileTest2().test();
}
}
上述代码运行时,我们期望的结果是出Exception。
如果第8行lower=value和第13行upper=value上打上断点,就会出现我们不希望出现的结果。lower:5,upper:3
正确的做法时将setLower和setUpper改成同步方法。
应该使用volatile的场合:
public class VolatileTest3 {
private long temp = 0x7fffffff00000000L;
private boolean running = true;
public void test() {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
long a;
while (running) {
a = temp;
if (a != 0x7fffffff00000000L && a != 0x00000000ffffffffL) {
System.out.println(Long.toHexString(a));
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
temp = 0x7fffffff00000000L;
}
running = false;
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
temp = 0x00000000ffffffffL;
}
running = false;
}
});
t1.start();
t2.start();
t3.start();
}
public static void main(String[] args) {
new VolatileTest3().test();
}
}
上述代码的运行结果,我们期望是没有输出,但是实际运行结果是有输出。输出0或7fffffffffffffff,每次运行结果都不一样。
正确的做法是将temp变量变为volatile。