简体   繁体   English

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

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

I often see the following pattern.我经常看到以下模式。 One thread will initialize the client in init() method in synchronized block.一个线程将在同步块的 init() 方法中初始化客户端。 All the other threads, also called init() method before they start to use the other class methods.所有其他线程在开始使用其他 class 方法之前也称为 init() 方法。 Client value is not changed after initialization.初始化后客户端值不会改变。 They dont set the client value as volatile.他们不会将客户价值设置为易失性。

My question is that if this is correct to do?我的问题是,如果这样做是正确的? Do all of the threads that create client, and call init() method, will after init method finished see the correct initilized value that was initialized byt the first thread that called init() method?创建客户端并调用 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?
  }
}

The rationale behind that pattern is that they think that because they read the client under synchronized block in init() method, the client will be set to the correct initialized value, and because the client is never changed, they can use it without volatile or synchronized after.该模式背后的基本原理是,他们认为因为他们在 init() 方法中的同步块下读取客户端,客户端将被设置为正确的初始化值,并且由于客户端永远不会改变,他们可以在没有 volatile 或后同步。 IS this correct assumption?这是正确的假设吗?

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 and used it without synchronization after. 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并在没有同步的情况下使用它。

It is only safe to do this if you are guaranteed to have called init() before calling insert(data) .只有当您保证在调用insert(data)之前调用了init()时,这样做才是安全的。

There is a happens-before edge created by the synchronized block: the end of the synchronized block happens before the next invocation of the same block.同步块创建了一个happens-before边: synchronized块的结束发生在下一次调用同一块之前。

This means that if a thread has invoked init() , then either:这意味着如果一个线程调用了init() ,那么:

  • client was previously uninitialized, so it is initialized on this call. client以前未初始化,因此在此调用时对其进行了初始化。
  • client was previously initialized, and the write to client is has happened before the current thread enters the synchronized block. client之前已初始化,并且在当前线程进入synchronized块之前已经发生了对client的写入。

No further synchronization is then necessary, at least with respect to client .然后不需要进一步的同步,至少对于client而言。

However, if a thread doesn't call init() , then there are no guarantees as to whether client is initialized;但是,如果线程调用init() ,则无法保证client是否已初始化; and no guarantee as to whether the client initialized by another thread (one that did call init() ) will be visible to the current thread (the one that didn't call init() ).并且不能保证由另一个线程(调用init()的线程)初始化的client是否对当前线程(未调用init()的线程)可见。

Relying on clients to call init() first is brittle.依赖客户端首先调用init()是脆弱的。 It would be much better either to use an eagerly-initialized field:使用热切初始化的字段会更好:

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.
  }
}

or, if you must do it lazily, use a lazy holder:或者,如果您必须懒惰地这样做,请使用懒惰的持有者:

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.
  }
}

Or, of course, chuck in a call to init() inside the insert method:或者,当然,在insert方法中调用init()

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

The disadvantage of the latter approach is that all threads must synchronize.后一种方法的缺点是所有线程都必须同步。 In the other two approaches, there is no contention after the first invocation.在其他两种方法中,第一次调用后没有争用。

It looks like the rationale behind this type of pattern is to ensure that you can only have one instance of Client in the application.看起来这种模式背后的基本原理是确保您在应用程序中只能有一个Client实例。 Multiple invocations (parallel/sequential) of init() method on different/same DB objects will not allow creating a new Client if it is already created and synchronized block is just to ensure that client object will be created only once if multiple threads called init() parallelly.对不同/相同数据库对象的init()方法的多次调用(并行/顺序)如果已经创建并且synchronized block只是为了确保客户端 object 将仅在multiple threads调用init()时创建一次,则不允许创建新客户端init()并行。

But it has nothing to do with safe call of insert() method on client object and that totally depends on the implementation of the insert() method that may be thread-safe or may not be.但这与客户端 object 上的insert()方法的安全调用无关,这完全取决于 insert() 方法的实现,该方法可能是线程安全的,也可能不是线程安全的。

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

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