簡體   English   中英

在同一函數的synchronized()塊中初始化之后,是否可以在synchronized()之外安全地使用Java集合?

[英]Can a Java collection be safely used outside of synchronized() after initializing in a synchronized() block in the same function?

下面的代碼是否被認為是線程安全的,即:是否保證在讀取列表之前發生寫入列表? 我一直試圖了解這在Java內存模型中是否會被認為是安全的,但目前還不清楚。

通過基本的流分析,看起來保證所有可能的線程必須在下面的for循環之前通過synchronized初始化塊,但是對該列表的迭代是確定性的還是線程安全的? 在使用下面的列表之前 ,我不確定是否可以保證初始化。

假設這是該類中唯一的方法。 我知道在synchronized塊中移動迭代會保證線程安全,但我更想知道這個構造是否安全。

此外,假設列表永遠不會逃避類。

Java內存模型在JLS中有解釋: http//docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4

private List<Foo> list;
private final Object monitor = new Object();

public void bar() {
    synchronized (monitor) {
        if (list == null) {
            list = new ArrayList<>();
            list.add(...); // expensive operation
            list.add(...); // expensive operation
            list.add(...); // expensive operation
        }
    }

    for (Foo foo : list) {
        // do something with foo
    }
}

當且僅當這是您在結構上修改列表的唯一地方時,它是線程安全的。

如果你在其他地方修改列表(例如使用clear() ), 即使其他地方也使用了synchronized ,那么在迭代它時可以很容易地修改列表。

如果您打算在其他地方修改列表,那么使用Collections.unmodifiableList()來確保(並記錄)這個事實可能是一個好主意。

JLS#17.4.5保證:

監視器上的解鎖發生在該監視器上的每個后續鎖定之前。

它還保證同步塊不能同時執行,也不能用for循環重新排序。

因此,第一個到達並獲取監視器的線程(讓我們稱之為T0)將初始化列表。 當T0退出同步塊時,線程內語義保證for循環將在T0中按預期執行。

隨后到達的所有線程將等待監視器可用,獲取它並且由於上面的保證將看到由T0初始化的列表(即,不為空並且已填充)。 並且由於線程內語義,for循環將按預期執行。

結論 :如果您的列表未寫入其他位置並且所有讀取都在獲取監視器后完成,則您的代碼是安全的。

如果您從未在同步方法中再次修改列表,則您公開的代碼是安全的。 現在,如果某個其他方法(不是bar())使用相同的列表,那么您的代碼就不安全了。 此外,您應該聲明final List list

synchronized(list)僅適用於其中包含的代碼塊。 如果另一個線程在使用for each循環遍歷列表時修改列表,則會出現問題。

暫無
暫無

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

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