java 关于volatile读操作与volatile修饰引用类型变量的问题?

Fear1es5ness 2016-10-17 09:31:34
我看很多人说对volatile的读操作一定是去主存中读取。

而我的理解是:写操作本身会有一个lock指令或者说StoreLoad屏障,意味着立即将写入的值刷入主存并通过MESI协议告知其它线程对于该变量缓存的无效,从而重新去主存中获取。
也就是说读操作只有在MESI将缓存行置为无效的时候才会去主存读取啊,如果没有volatile写的话,应该是不会每次都去主存中获取吧?

关于这一点有个另外的问题我需要问一下:
很多的文章中都有说到,如果volatile修饰一个引用类型变量,那么对该变量指向的对象的域是不具有volatile的性质。这一点我没意见。从汇编来看确实没有对volatile对象的域的操作有lock指令。
但是比如有如下定义:

package test;

public class Test implements Runnable {

static class ObjectA {
private boolean flag = true;
}

private volatile ObjectA a;

public Test(ObjectA a) {
this.a = a;
}

@Override
public void run() {
while (a.flag) {
// i++;
// System.out.println(a.flag);
}
}

public void stop() {
a.flag = false;
}

public static void main(String[] args) throws InterruptedException {
Test test = new Test(new ObjectA());
Thread t = new Thread(test);
t.start();

Thread.sleep(1000);// 保证线程已启动
test.stop();
t.join();
}
}


可以看到如果a是volatile的话线程可以结束。
但是如果a不是volatile的话,线程将一直无法结束(如果你的CPU很慢还是有可能会结束的),因为没有volatile修饰的话a.flag一直读的是缓存,而非主存值,除非cpu有空才会去重新获取flag的值,比如在循环体内加一些复杂操作,new个对象sysout以下什么的。

当然确实因为flag这个变量不是volatile,所以a.flag = false这个写操作并不能保证可见性这一点我没有疑问。

但是为什么a.flag能够得知flag的修改呢?按照一开始我说的,难道不应该是只有对volatile的写操作才会导致MESI协议通知其他CPU该缓存无效,从而去主存中获取值吗?而a.flag = false并没有改变a本身引用,那么实际上while(a.flag)中对a引用的读取应该不会去主存当中读取,而对flag的读取自然也就是读的缓存,换句话说它不应该能够得知其域值的改变。

那么只有1个解释:
每一次对a引用的读取都会无条件的去主存当中获取a的引用地址也就是对象,然后将CPU中缓存的flag域值更新,最终可以得到正确的flag域值。但这样一来就表示对于volatile读来说一定不会读缓存,无论是否有其它的线程对该变量有过修改。
当然上面的解释就和问题一开始我对于volatile写的理解产生冲突了


按照上面这种说法我是不是可以理解为:volatile引用类型变量的域(对于对象)或元素(对于数组),如果没有声明volatile的话,其写操作是不具备volatile写特性的,但是其读操作却具有volatile读特性。所以严格来说,实际上这些域和元素是不具备完全的可见性的(只有等写操作线程CPU心情好了,将写入值写回主存的时候,读线程可以立即读取到)。


多谢指教!如有不明请指出,谢谢!
...全文
780 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
哈希塞特 2019-10-09
  • 打赏
  • 举报
回复
volatile的效果你能靠写java代码能模拟出来吗?
阿萨德执行 2019-09-30
  • 打赏
  • 举报
回复
引用 6 楼 阿萨德执行 的回复:
同样的疑惑啊,我理解的,volatile修饰,java并没有使用MESI协议,也就是说并不会强制缓存行不可用,只是在写的时候,更具lock前缀标志,将当前线程操作的值,强制写入到主存。这样volatile其实并不具有原子性。
也就是只是在读取和写的时候,都是强制,读取写入主存。不知道理解的对不对
阿萨德执行 2019-09-30
  • 打赏
  • 举报
回复
同样的疑惑啊,我理解的,volatile修饰,java并没有使用MESI协议,也就是说并不会强制缓存行不可用,只是在写的时候,更具lock前缀标志,将当前线程操作的值,强制写入到主存。这样volatile其实并不具有原子性。
sinat_14861273 2018-12-25
  • 打赏
  • 举报
回复
看java内存模型对volatile定义的规则,每次获取变量都必须从主内存刷新到最新的值。java虚拟机只是定义了规范,您说的好像是具体实现了,或者比较底层了。不知道理解的对不对。
verejava 2018-07-19
  • 打赏
  • 举报
回复
并发编程 之 volatile 关键字

http://www.verejava.com/?id=1734028422785
花溪的小石头 2018-07-19
  • 打赏
  • 举报
回复
你的这个问题问的好,不过99%的人都不是真懂的,要请并发比较了解的大神来回答。

我也是一直困惑于你所提的这个问题
Fear1es5ness 2016-10-18
  • 打赏
  • 举报
回复
引用 1 楼 zs808 的回复:
volatile读a的时候对a加锁了
读volatile变量是不会加锁的。你用hsdis看一下汇编,只有volatile写操作有个lock前缀,而读是没有的。
zs808 2016-10-18
  • 打赏
  • 举报
回复
volatile读a的时候对a加锁了

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧