V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
honam
V2EX  ›  Java

一直有个共享变量可见性的问题搞不懂,进来聊聊憋

  •  
  •   honam · 2016-05-26 10:37:53 +08:00 · 3326 次点击
    这是一个创建于 3139 天前的主题,其中的信息可能已经有所发展或是发生改变。
    public class NoVisibility{
    	private static boolean ready;
    	private static int number;
    
    	private static class ReaderThread extends Thread{
    		public void run(){
    			while(!ready){
    				Thread.yield();
    			}
    			System.out.println(number);
    		}
    	}
    
    	public static void main(String[] args){
    		new ReaderThread().start();
    		number = 42;
    		ready = true;
    	}
    }
    

    上代码,这是并发编程实践里面的一个例子(清单 3.1 ),然后这段代码可能出现这种情况:可能一直保持循环。“因为对于读线程来说, ready 的值可能永远不可见”。我就纳闷了,怎么会永远不可见?你说一阵子不可见我能理解,真的会永远不可见吗?

    10 条回复    2016-05-27 02:07:28 +08:00
    doing
        1
    doing  
       2016-05-26 11:35:05 +08:00
    每个线程有自己单独的工作内存,操作 ready 变量 会把 ready 变量从主内存 复制到自己的工作内存中去操作。操作完后,会写回主内存。

    在这个过程,可能就会不可见。 A 线程改了 ready 变量的值,但还没写回主内存。或者,新的 B 线程还没重新去主内存获取 ready 变量。

    我是这样理解的。
    honam
        2
    honam  
    OP
       2016-05-26 11:59:58 +08:00
    @doing 感谢你的回答,“ A 线程改了 ready 变量的值,但还没写回主内存。或者,新的 B 线程还没重新去主内存获取 ready 变量。 ”是会这样,但是终究会写回内存 或者 会去主内存获取新值到工作内存吧?这两个操作难道都有可能会被终止掉?
    incompatible
        3
    incompatible  
       2016-05-26 12:04:46 +08:00 via iPhone
    @honam 这个循环中的 ready 在编译后似乎会被优化成局部产量,初始化一次后就不变了,永远拿不到其他线程修改后的值。
    honam
        4
    honam  
    OP
       2016-05-26 12:08:37 +08:00
    @incompatible 谢谢回答,顺求相关资料,书里面一句带过没说原因好头痛。
    incompatible
        5
    incompatible  
       2016-05-26 12:26:34 +08:00 via iPhone
    @honam 周志明「深入理解 Java 虚拟机」,里面有一整章是讲这个的。
    incompatible
        6
    incompatible  
       2016-05-26 12:30:09 +08:00 via iPhone
    @honam 我上面说编译期被优化不是主要原因。主要原因还是 1 楼说的那样。 A 和 B 操作之间没有 Barrier ,所以 B 读不到 A 的更改。给 ready 加了 volatile 后就产生了一个 Barrier , B 就可以读到了。
    honam
        7
    honam  
    OP
       2016-05-26 12:42:48 +08:00
    @incompatible 我再问一个 volatile 的问题,如果变量不加这个修饰关键字,这个变量是不是不会在某个线程的工作内存失效,或者说,不会去主内存里面拿新的值?如果是我就明白了,如果不是,那还是回到我回答 1 楼的问题。。。
    hadixlin
        8
    hadixlin  
       2016-05-26 12:57:44 +08:00
    @honam 这个答案是不确定的,依赖 JVM 的具体实现.但是 volatile 的语义是虚拟机规范里面写明的,所有的 JVM 都必须实现该语义.
    所以不用纠结变量什么时候去主存拿新值,只要知道没有 volatile 或者其他内存屏障,被多线程共享的变量的值是不可靠的.
    anexplore
        9
    anexplore  
       2016-05-26 13:21:45 +08:00
    我的考虑是,在 run()中 while(!ready)并没有改变 ready 的值,那么编译器是否会直接将其优化成 tmp = !ready; while(tmp),从而导致其一直运行
    SoloCompany
        10
    SoloCompany  
       2016-05-27 02:07:28 +08:00   ❤️ 1
    没有 violate 也没有 synchronized 当然有可能永远看不见变化
    但具体行为依赖于 jvm 实现而不是编译器
    在真正的多核环境下并且每个核心都有独立缓存的执行条件下, ReaderThread 线程执行的工作区内存有可能永远都得不到更新
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   961 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 19:11 · PVG 03:11 · LAX 11:11 · JFK 14:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.