关于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的变量被写后, 后面所有的读,都能看到这个写的值.
Global site tag (gtag.js) - Google Analytics