[英]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.