繁体   English   中英

在所有线程的同步块中初始化 static 变量,读取时不同步

[英]Init static variable in synchronized block for all the threads, read without synchronized

我经常看到以下模式。 一个线程将在同步块的 init() 方法中初始化客户端。 所有其他线程在开始使用其他 class 方法之前也称为 init() 方法。 初始化后客户端值不会改变。 他们不会将客户价值设置为易失性。

我的问题是,如果这样做是正确的? 创建客户端并调用 init() 方法的所有线程是否会在 init 方法完成后看到由调用 init() 方法的第一个线程初始化的正确初始化值?

public class DB {

  private static Object lock = new Object();
  private static Client client;

  public init() {

    synchronized (lock) {
      if (client != null) {
        return;
      }
      client = new Client();
    }
  }

  public insert(Object data) {
    client.insert(data); // is this ok to access the client without volatile or synchronized?
  }
}

该模式背后的基本原理是,他们认为因为他们在 init() 方法中的同步块下读取客户端,客户端将被设置为正确的初始化值,并且由于客户端永远不会改变,他们可以在没有 volatile 或后同步。 这是正确的假设吗?

You can see this pattern for example here: https://github.com/brianfrankcooper/YCSB/blob/cd1589ce6f5abf96e17aa8ab80c78a4348fdf29a/mongodb/src/main/java/site/ycsb/db/MongoDbClient.java where they initialized the database in init method并在没有同步的情况下使用它。

只有当您保证在调用insert(data)之前调用了init()时,这样做才是安全的。

同步块创建了一个happens-before边: synchronized块的结束发生在下一次调用同一块之前。

这意味着如果一个线程调用了init() ,那么:

  • client以前未初始化,因此在此调用时对其进行了初始化。
  • client之前已初始化,并且在当前线程进入synchronized块之前已经发生了对client的写入。

然后不需要进一步的同步,至少对于client而言。

但是,如果线程调用init() ,则无法保证client是否已初始化; 并且不能保证由另一个线程(调用init()的线程)初始化的client是否对当前线程(未调用init()的线程)可见。

依赖客户端首先调用init()是脆弱的。 使用热切初始化的字段会更好:

public class DB {

  private static final Client client = new Client();

  public insert(Object data) {
    client.insert(data);  // Guaranteed to be initialized once class loading is complete.
  }
}

或者,如果您必须懒惰地这样做,请使用懒惰的持有者:

public class DB {
  private static class Holder {
    private static final Client client = new Client();
  }

  public insert(Object data) {
    Holder.client.insert(data);  // Holder.client is initialized on first access.
  }
}

或者,当然,在insert方法中调用init()

  public insert(Object data) {
    init();
    client.insert(data);
  }

后一种方法的缺点是所有线程都必须同步。 在其他两种方法中,第一次调用后没有争用。

看起来这种模式背后的基本原理是确保您在应用程序中只能有一个Client实例。 对不同/相同数据库对象的init()方法的多次调用(并行/顺序)如果已经创建并且synchronized block只是为了确保客户端 object 将仅在multiple threads调用init()时创建一次,则不允许创建新客户端init()并行。

但这与客户端 object 上的insert()方法的安全调用无关,这完全取决于 insert() 方法的实现,该方法可能是线程安全的,也可能不是线程安全的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM