问题 在Java synchronized块中,是在所有字段上显示还是只在同步变量上写入?


说你有这个代码:

private String cachedToken;
private final Object lockObject = new Object();

....


retrieveToken(){
 synchronized(lockObject){
  if (cachedToken == null){
   cachedToken = goGetNewToken();
  }
  return cachedToken;
 }
}

请写信给 cachedToken 对于已锁定的所有线程都可见 lockObject


10743
2018-05-20 15:45


起源

是的,这就是重点...但是写入可能发生在CPU的l1或l2缓存中,并且在另一个线程获得锁定之前不会刷新到主内存。 - Jonathan S. Fisher
这不是真的。如果另一个线程锁定 lockObject 在另一个线程写入然后离开之后,进入的线程将看到写入。 - John Vint
注意:经常写入“synchronized变量” 坏。代码在对象上同步,而不是变量。如果在中游改变变量以引用不同的对象,则在该变量引用的对象上同步的多个块可以同时执行。 - Andy Thomas
@ AndyThomas-Cramer对,这就是为什么我有一个单独的 lockObject 而不是使用cachedToken。我可以将该字段设为final,以确保引用永远不会改变。 - Jonathan S. Fisher


答案:


是。在lockObject上同步会在关系之前建立一个Happens(也就是设置一个内存屏障)。这意味着随后获得锁定的所有线程将看到先前保持锁定时发生的任何更改。

但是,对于它的价值,你的延迟初始化的实现是有缺陷的。这是正确的方法:

private volatile String cachedToken;

retrieveToken() {
    if (cachedToken == null) {
        synchronized(lockObject) {
            if (cachedToken == null) {
                cachedToken = goGetNewToken();
            }
        }
    }
    return cachedToken
}

这样,当Threads首次开始请求时,您只需要少量锁定。之后,cachedToken将不为null,您将不需要同步。


10
2018-05-20 15:48



那个内存屏障将包括所触及的任何字段,而不仅仅是我同步的对象? - Jonathan S. Fisher
@Tom McIntyre你的做法实际上是不安全的。看这个: cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html - Mik378
是的,否则共享状态并发将比现在更加困难! - Tom McIntyre
@ Mik378啊是的,我见过。在底部有一个小注释,它补充说,使字段变为volatile可确保它自java 5开始工作。我已使用volatile更新了我的答案。 - Tom McIntyre
谢谢:)其次,我的方式“破碎”还是效率低下?看起来每次获取锁定都很昂贵,但它也是“正确的”。 - Jonathan S. Fisher


当然, synchronize确保两件事:

  • 原子性
  • 整个对象的内存障碍(在您的情况下您所期望的)

例如, volatile确保内存屏障,但不处理原子性。


6
2018-05-20 15:51



除了读取和写入多头或双头之外,其中不稳定的DOES确保原子性。 - Tom McIntyre
@Tom McIntyre是的,但这不是因为 volatile关键字本身。 - Mik378
你是怎么说'不是由于挥发性关键词本身'? - Tom McIntyre
@Tom它主要是由于编译器的工作方式与大型原始如长,双等等。当然,放置 volatile 在...上 int 像一个人 AtomicInteger 但最好忘记这个具体案例,并始终避免 volatile对于原子能需求而不是在需要之前发生。 - Mik378
volatile和AtomicXXX有不同的用例。而volatile确实可以确保原子读取和写入双精度和长数。它与编译器无关。 - Tom McIntyre