簡體   English   中英

Java:不同線程對非易失性變量的緩存

[英]Java: Caching of non-volatile variables by different threads

情況如下:

  1. 我有一個 object 有很多 setter 和 getter。
  2. 此 object 的實例是在一個特定線程中創建的,其中所有值都已設置。 最初,我使用 new 語句創建了一個“空” object,然后我才根據一些復雜的遺留邏輯調用一些設置方法。
  3. 只有這樣,這個 object 才可用於僅使用 getter 的所有其他線程。

問題:我是否必須使此 class 的所有變量都易失?

關注點:

  • object 的新實例的創建和其所有值的設置是及時分開的。
  • 但是所有其他線程在設置所有值之前都不知道這個新實例。 所以其他線程不應該有未完全初始化的object的緩存。 不是嗎?

注意:我知道構建器模式,但由於其他幾個原因,我不能在那里應用它:(

編輯:由於我覺得 Mathias 和 axtavt 的兩個答案不太匹配,所以我想添加一個示例:

假設我們有一個foo class:

class Foo {   
    public int x=0;   
}

並且兩個線程正在使用它,如上所述:

 // Thread 1  init the value:   
 Foo f = new Foo();     
 f.x = 5;     
 values.add(f); // Publication via thread-safe collection like Vector or Collections.synchronizedList(new ArrayList(...)) or ConcurrentHashMap?. 

// Thread 2
if (values.size()>0){        
   System.out.println(values.get(0).x); // always 5 ?
}

據我了解Mathias,它可以根據JLS在一些JVM上打印出0。 據我了解 axtavt 它總是會打印 5。

你有什么意見?

——問候,德米特里

在這種情況下,在使 object 可用於其他線程時,您需要使用安全發布習慣用法,即(來自Java Concurrency in Practice ):

  • 從 static 初始化程序初始化 object 引用;
  • 將對它的引用存儲到 volatile 字段或 AtomicReference 中;
  • 將對其的引用存儲到正確構造的 object 的最終字段中; 或者
  • 將對它的引用存儲到由鎖正確保護的字段中。

如果您使用安全發布,則不需要聲明字段volatile

但是,如果您不使用它,則聲明字段volatile (理論上)將無濟於事,因為volatile引起的 memory 障礙是一方面:volatile write 可以在其后使用非易失性操作重新排序。

因此, volatile在以下情況下確保正確性:

class Foo {
    public int x;
}
volatile Foo foo;

// Thread 1
Foo f = new Foo();
f.x = 42;
foo = f; // Safe publication via volatile reference

// Thread 2
if (foo != null)
     System.out.println(foo.x); // Guaranteed to see 42

但在這種情況下不起作用:

class Foo {
    public volatile int x;
}
Foo foo;

// Thread 1
Foo f = new Foo();
// Volatile doesn't prevent reordering of the following actions!!!
f.x = 42;
foo = f;

// Thread 2
if (foo != null)
     System.out.println(foo.x); // NOT guaranteed to see 42, 
                                // since f.x = 42 can happen after foo = f

從理論的角度來看,在第一個樣本中存在一個可傳遞的發生前關系

f.x = 42 happens before foo = f happens before read of foo.x 

在第二個示例中, fx = 42foo.x的讀取沒有通過happens-before關系鏈接,因此它們可以按任何順序執行。

在讀取該字段的線程上調用start方法之前,您無需聲明字段 volatile 的值已設置。

原因是在這種情況下,設置與另一個線程中的讀取處於先發生關系(如 Java 語言規范中所定義)。

JLS的相關規則是:

  • 線程中的每個動作都發生在該線程中的每個動作之前
  • 在線程上啟動的調用發生在已啟動線程中的任何操作之前。

但是,如果在設置字段之前啟動其他線程,則必須將字段聲明為 volatile。 JLS 不允許您假設線程在第一次讀取值之前不會緩存該值,即使在 JVM 的特定版本上可能是這種情況。

為了完全了解發生了什么,我一直在閱讀有關 Java Memory Model (JMM) 的信息。 可以在 Java 實踐中的並發中找到對 JMM 的有用介紹。

我認為問題的答案是:是的,在給出的示例中,使 object 的成員易失是不必要的。 但是,這種實現相當脆弱,因為這種保證取決於完成事情的確切順序以及容器的線程安全性。 構建器模式將是一個更好的選擇。

為什么保證:

  1. 線程 1 在將值放入線程安全容器之前完成所有分配。
  2. 線程安全容器的 add 方法必須使用一些同步構造,如 volatile 讀/寫、lock 或 synchronized()。 這保證了兩件事:

    1. 同步之前在線程 1 中的指令實際上會在之前執行。 也就是說,JVM 不允許使用同步指令重新排序指令以進行優化。 這稱為發生前保證。
    2. 在線程 1 中同步之前發生的所有寫入隨后對所有其他線程都是可見的。
  3. 對象在發布后永遠不會被修改。

但是,如果容器不是線程安全的,或者事物的順序被不知道該模式的人更改了,或者對象在發布后被意外更改,則不再有任何保證。 因此,遵循 Builder 模式,可以由 google AutoValue 或 Freebuilder 生成,這樣會更安全。

這篇文章的主題也相當不錯: http://tutorials.jenkov.com/java-concurrency/volatile.html

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM