volatile时轻量级的synchronized,它在多处理器开发中保证了数据的读的一致性,意思就是当一个线程修改一个共享变量时,另外一个线程能读到这个共享变量的值。如果volatile变量修饰符使用的恰当的话,他的运行成本会大大降低,因为他不会引起上下文的切换和调度,因为他并不会阻塞线程,也因此他不能保证多个线程对数据进行写操作时的安全性(即原子性)。
volatile不能保证原子性:
所谓原子性就是,要么都全部过程都执行成功,要么一个都执行不成功。
例如:i++这个操作分为三步
- 从内存中取出i
- 对i执行+1操作
- 将i放回内存中
当它只执行了前两步后就中断了,因此前两步执行成功,但是最后一步失败,i没有回滚减1,因此他不满足原子性,请看如下实例:
public abstract class Test {
public static void main(String[] args) {
MyThread[] mythreadArray = new MyThread[100];
for (int i = 0; i < 100; i++) {
mythreadArray[i] = new MyThread();
}
for (int i = 0; i < 100; i++) {
mythreadArray[i].start();
}
}
}
class MyThread extends Thread {
public volatile static int count;
private static void addCount() {
for (int i = 0; i < 100; i++) {
count++;
}
System.out.println("count=" + count);
}
@Override
public void run() {
addCount();
}
}
结果:
按理来说,他应该最后的输出结果时100*100的,这里说明了volatile并不能保证原子性。
那么volatile是怎么实现的呢?
先来看一下volitile的定义是什么,在JAVA语言规范第三版中对volatile的定义如下:Java编程语言允许多线程访问共享变量,为了确保共享变量能被准却和一致地更新 ,线程应该确保通过排他锁单独的获取这个变量。
他通过两步实现其功能:
1、线程通过调用cpu修改值之后,会将缓存行中的数据写回到内存中去。在执行写回操作时,处理器可以独占任何共享空间,而且最近的处理器,lock#信号不会锁总线,而是锁缓存,锁住总线的话,占用的资源太大。
2、写回操作会使在其他cpu里缓存了该内存地址的数据无效。例如在Pentium和P6 faminly处理器中,如果通过嗅探一个处理器来检测其他处理器打算写的内存地址,而这个地址处于共享状态,那么正在嗅探的处理器将会使其自身的缓存行无效,下次访问相同的内存地址时,强制执行缓存行填充。