在Java同步块中,写入是在所有字段上可见还是仅在同步变量上可见?

在Java同步块中,写入是在所有字段上可见还是仅在同步变量上可见?,java,concurrency,synchronization,Java,Concurrency,Synchronization,假设您有以下代码: private String cachedToken; private final Object lockObject = new Object(); .... retrieveToken(){ synchronized(lockObject){ if (cachedToken == null){ cachedToken = goGetNewToken(); } return cachedToken; } } 写入cachedToken是否对所有

假设您有以下代码:

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

....


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

写入
cachedToken
是否对所有锁定了
lockObject
的线程可见?

是。lockObject上的同步建立了“发生在之前”关系(aka设置了内存屏障)。这意味着随后获得锁的所有线程都将看到先前持有锁时发生的任何更改

尽管如此,您的延迟初始化实现还是有缺陷的。这是正确的方法:

private volatile String cachedToken;

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

这样,当线程第一次开始请求锁时,只需获得少量锁。在此之后,cachedToken将不会为空,您将不需要同步。

当然,
同步
确保两件事:

  • 原子性
  • 整个对象上的内存屏障(您在本例中所期望的)

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

是的,这就是重点。。。但是写操作可能发生在CPU的一级或二级缓存上,并且在另一个线程获得锁之前不会刷新到主内存。这不是真的。如果在另一个线程写入后,另一个线程锁定了
lockObject
,则进入的线程将看到写入。注意:对“synchronized variable”的写入通常是错误的。代码在对象上同步,而不是在变量上同步。如果变量在中途更改为引用其他对象,则在该变量引用的对象上同步的多个块可以同时执行。@Andythonas Cramer right,这正是我使用单独的
lockObject
而不是使用cachedToken的原因。我可以将该字段设置为最终字段,以确保引用永远不会更改。并且该内存屏障将包括任何已触及的字段,而不仅仅是我同步的对象?@Tom McIntyre您的做法实际上是不安全的。看看这个:是的,否则共享状态并发将比现在更加困难@是的,我看到了。在底部有一个小注释,它补充说,使字段易变可确保自Java5以来它可以工作。我已经用使用的volatile更新了我的答案。谢谢:)第二,我的方式是“坏了”还是效率低下?似乎每次获取锁都很昂贵,但它也是“正确的”。除了对long或double进行读写之外,volatile确实确保了原子性。@Tom McIntyre是的,但这不是由于volatile关键字本身造成的。你怎么说“不是由于volatile关键字本身造成的”?@Tom这主要是由于编译器使用long、double等大型原语造成的。。当然,将
volatile
放在
int
上就像是一个
AtomicInteger
但是最好忘记这个特殊情况,并且总是避免
volatile
用于原子性需求,而不是在需求之前发生。volatile和AtomicXXX有不同的用例。volatile确实确保了原子对double和long的读写。它与编译器无关。