簡體   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