关于volatile关键字的疑惑
pingyuyue
2012-02-21
最近看《深入理解Java虚拟机:JVM高级特性与最佳实践》P329这样写到,“普通变量与volatile的区别是volatile的特殊规则保证了新值能立刻同步到主存,以及每次使用前立即从内存刷新”
个人觉得:即使是 isFull=false这个赋值操作,一个线程A修改了isFull的值,其它线程B也可能拿到过期的值,原因是在线程A修改isFull修改之前,其它线程B进行了取值原子操作,所以volatile即使做个开关操作也会有问题,按照上述逻辑,是在感觉volatile所起的作用太小了,大家给点看法吧 |
|
rockyfxl
2012-02-21
volatile 保证了在A在写之前, B已经得到通知。
对于标志位来说,我们是想让其它线程在 标志修改之后 可以正确得到值。 但肯定也是之后,线程A 修改之前, B拿到的肯定是false, 但一旦A 改了后,B立刻就得到更新。 如果不是volatile, A修改后,B可能看到的还是false. 个人理解,还请指教。 |
|
nkhanxh
2012-02-21
我觉得rocky说的对。
我认为是作用是防止A更新而b较长期拿不到的情况,比如缓存在寄存器里面。 但是不可能防止你说的情况。“原因是在线程A修改isFull修改之前,其它线程B进行了 取值操作,”这种情况即使是加锁也不行吧,a修改了之前b读取,肯定不行啊。 除非a一直锁住,要求自己至少写一次之后b才读。然后读之后立即把某个标志位reset。 pingyuyue 写道 最近看《深入理解Java虚拟机:JVM高级特性与最佳实践》P329这样写到,“普通变量与volatile的区别是volatile的特殊规则保证了新值能立刻同步到主存,以及每次使用前立即从内存刷新”
也就是说其 当一个线程修改了volatile变量的值,它线程再次读的话能立马看到新的值(强制从内存获取刷新后的值), 而普通变量起不了这个作用; 个人觉得:即使是 isFull=false这个赋值操作,一个线程A修改了isFull的值,其它线程B也可能拿到过期的值,原因是在线程A修改isFull修改之前,其它线程B进行了 取值操作,所以volatile即使做个开关操作也会有问题,按照上述逻辑,是在感觉volatile所起的作用太小了,大家给点看法吧 |
|
pingyuyue
2012-02-21
rockyfxl 写道 volatile 保证了在A在写之前, B已经得到通知。
对于标志位来说,我们是想让其它线程在 标志修改之后 可以正确得到值。 但肯定也是之后,线程A 修改之前, B拿到的肯定是false, 但一旦A 改了后,B立刻就得到更新。 如果不是volatile, A修改后,B可能看到的还是false. 个人理解,还请指教。 按照您的理解,volatile完全可以做计数器使用了,对吧?你可以写个计数器程序试试? public class VolatileTest { public static volatile int race = 0; public static void increase() { race++; } private static final int THREADS_COUNT = 20; /** * @param args */ public static void main(String[] args) { Thread[] threads = new Thread[THREADS_COUNT]; for (int i = 0; i < THREADS_COUNT; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10000; i++) { increase(); } } }); threads[i].start(); } while (Thread.activeCount() > 1) { Thread.yield(); System.out.println(race); } } } |
|
rockyfxl
2012-02-21
楼上的计数器是不行的。
线程A 拿到race=0 线程B 拿到race=0 线程A +1, 线程B 得到更新的值。 但是问题 线程B的 race+=1 不是个原子操作: 0 getstatic jvm.test.VolatileTest.race : int [13] 3 iconst_1 4 iadd 5 putstatic jvm.test.VolatileTest.race : int [13] 所以结果不一定是从A更新的值上+1,可能是从上次拿到的值+1. 解释的不知道可准确,还请指教。 |
|
pingyuyue
2012-02-21
rockyfxl 写道 楼上的计数器是不行的。
线程A 拿到race=0 线程B 拿到race=0 线程A +1, 线程B 得到更新的值。 但是问题 线程B的 race+=1 不是个原子操作: 0 getstatic jvm.test.VolatileTest.race : int [13] 3 iconst_1 4 iadd 5 putstatic jvm.test.VolatileTest.race : int [13] 所以结果不一定是从A更新的值上+1,可能是从上次拿到的值+1. 解释的不知道可准确,还请指教。 你对计数器不正确的原因解释我基本认同; “volatile 保证了在A在写之前, B已经得到通知。”这句不认同, |
|
pingyuyue
2012-02-21
pingyuyue 写道 rockyfxl 写道 楼上的计数器是不行的。
线程A 拿到race=0 线程B 拿到race=0 线程A +1, 线程B 得到更新的值。 但是问题 线程B的 race+=1 不是个原子操作: 0 getstatic jvm.test.VolatileTest.race : int [13] 3 iconst_1 4 iadd 5 putstatic jvm.test.VolatileTest.race : int [13] 所以结果不一定是从A更新的值上+1,可能是从上次拿到的值+1. 解释的不知道可准确,还请指教。 你对计数器不正确的原因解释我基本认同; “volatile 保证了在A在写之前, B已经得到通知。”这句不认同, 我觉得:引起问题的原因在于,线程使用某个变量的值,不一定每次都直接从内存取,有可能已经取出保存在工作内存了,然后发生了线程切换(变量某些中间状态不稳定) |
|
xiaoyu
2012-02-21
开关都是建议使用 while 而不是 if (这本书也有讲的吧, 没讲的话, 找一本java多线程编程来看吧).
volatile 保证了可见性, 但是不保证其他的. |
|
pingyuyue
2012-02-21
xiaoyu 写道 开关都是建议使用 while 而不是 if (这本书也有讲的吧, 没讲的话, 找一本java多线程编程来看吧).
volatile 保证了可见性, 但是不保证其他的. 这个“可见性”其实也是有点不安全的; 比如线程A和B,A修改了volatile变量i;B只有进行取值原子操作的时候才能感应到更新;但是B若在A修改i之前已经进行了取值原子操作,这样就不会感应到i已经变了,所以短暂的数据不一致; 个人理解,请指教 |
|
xiaoyu
2012-02-21
pingyuyue 写道 xiaoyu 写道 开关都是建议使用 while 而不是 if (这本书也有讲的吧, 没讲的话, 找一本java多线程编程来看吧).
volatile 保证了可见性, 但是不保证其他的. 这个“可见性”其实也是有点不安全的; 比如线程A和B,A修改了volatile变量i;B只有进行取值原子操作的时候才能感应到更新;但是B若在A修改i之前已经进行了取值原子操作,这样就不会感应到i已经变了,所以短暂的数据不一致; 个人理解,请指教 你理解有误... 你这样的假设永远都拿不到新值....保证可见性, 是值当volatile的变量被写后, 后面所有的读,都能看到这个写的值. |