package basic;
public class TestVolatile {
public volatile static int count = 0;
public static void increase() {
try {
// 延迟10毫秒,使得结果明显
Thread.sleep(10);
count++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
TestVolatile.increase();
}
}).start();
}
System.out.println("期望运行结果:10000");
System.out.println("实际运行结果:" + TestVolatile.count);
}
}
代码如下,由于volatile的read and load操作并不是原子性的,它只保证了从主内存读到栈内存的值是最新的,因此上述代码运行结果并不是语气的10000。
问题:
1.既然volatile并没有保证原子性,为什么还要使用它?
2.JDK中针对volatile的这种弊端,还做了哪些设计来弥补这一缺憾?
在任务的执行过程中,为了提高效率,有时会重排序初始化的过程,这在多线程并发的情况下或许会让程序不稳定,用volatile可以阻止重排序。
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
上面三行伪代码中的2和3之间,可能会被重排序(在一些JIT编译器上,这种重排序是真实发生的,详情见参考文献1的“Out-of-order writes”部分)。2和3之间重排序之后的执行时序如下:
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址
//注意,此时对象还没有被初始化!
ctorInstance(memory); //2:初始化对象
此时若有多线程并发访问,就有可能会出现对象还未初始化的情况。
而volatile关键字,会阻止这种优化,保证每次都会按照第一段代码的顺序执行。
来源:http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization
volatile
的大致作用@FreeBirdLjj已经总结性的说了下,补充几点
volatile
作用是保证了变量在不同线程之间的内存可见性和对单个volatile变量的读写的原子性,主要手段是在编译器生成指令序列时,插入特定类型的内存屏障来实现的volatile
内存的可见性,主要通过禁止重排序(编译器重排序,处理器重排序),保证缓存及时失效,保证缓存及时刷入主内存来实现的对单个
volatile
变量的读取和设值语义等价与
public synchronized A getA();
public synchronized void setA();
volatile
可以避免寄存器优化,而且在 Java 里还自带 barrier,虽然不能防止竞争,但可以用于跨线程的无竞争的同步。
如果数据还有竞争的话,就要考虑 Atomic 的数据类型了。不过最好尽量避免竞争,因为竞争会降低并发度。
你需要的是【锁】。