volatile的作用
1,保证可见性
2,保证cpu指令顺序执行
上面两个特性就不过多解释了
当volatile 修饰 int i 时,为什么不是线程安全呢?
如图:
由上,我们可以得出以下结论。
- read和load阶段:从主存复制变量到当前线程工作内存;
- use和assign阶段:执行代码,改变共享变量值;
- store和write阶段:用工作内存数据刷新主存对应变量的值。
在多线程环境中,use和assign是多次出现的,但这一操作并不是原子性,也就是在read和load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,也就是私有内存和公共内存中的变量不同步,所以计算出来的结果会和预期不一样,也就出现了非线程安全问题。
对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的,例如线程1和线程2在进行read和load的操作中,发现主内存中count的值都是5,那么都会加载这个最新的值。也就是说,volatile关键字解决的是变量读时的可见性问题 ,但无法保证原子性,对于多个线程访问同一个实例变量还是需要加锁同步。
例如下面方法 : void f1() { i++; }
cpu指令如下
void f1();
Code:
0: aload_0
1: dup
2: getfield #2; //Field i:I
5: iconst_1
6: iadd
7: putfield #2; //Field i:I
10: return
可见i++执行了多部操作, 从变量i中读取读取i的值 -> 值+1 -> 将+1后的值写回i中,这样在多线程的时候执行情况就类似如下了
Thread1 Thread2 r1 = i; r3 = i; r2 = r1 + 1; r4 = r3 + 1; i = r2; i = r4;
这样会造成的问题就是 r1, r3读到的值都是 0, 最后两个线程都将 1 写入 i, 最后 i 等于 1, 但是却进行了两次自增操作
可知加了volatile和没加volatile都无法解决非原子操作的线程同步问题。
正确使用 volatile 的模式
很多并发性专家事实上往往引导用户远离 volatile 变量,因为使用它们要比使用锁更加容易出错。然而,如果谨慎地遵循一些良好定义的模式,就能够在很多场合内安全地使用 volatile 变量。要始终牢记使用 volatile 的限制 —— 只有在状态真正独立于程序内其他内容时才能使用 volatile —— 这条规则能够避免将这些模式扩展到不安全的用例。
对于这里我的理解是,volatile可以用于多线程。但是使用必须保证,同一时间只有一个线程对volatile修饰的对象进行修改。其他线程只是读取 volatile修饰的对象值。