簡體   English   中英

為什么即使使用同步方法,此代碼也不是線程安全的?

[英]Why is this code not thread-safe, even when using a synchronized method?

為什么即使我們使用同步方法並因此在Helper對象上獲得鎖定,此代碼為何也不是線程安全的?

class ListHelper <E> {
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());

    public synchronized boolean putIfAbsent(E x) {
        boolean absent = !list.contains(x);
        if (absent)
            list.add(x);
        return absent;
    }
}

因為該列表在contains返回時解鎖,然后在調用add時再次鎖定。 其他可能在兩者之間添加相同的元素。

如果您打算只使用helper對象中的列表,則應將其聲明為private 如果這樣做,則只要列表的所有操作都通過在助手對象中同步的方法,代碼就將是線程安全的。 同樣值得注意的是,只要是這種情況,就不需要使用Collections.synchronizedList因為您可以在自己的代碼中提供所有必需的同步。

或者,如果要允許列表公開,則需要同步對列表的訪問,而不是對助手對象的訪問。 以下是線程安全的:

class ListHelper <E> {
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());

    public boolean putIfAbsent(E x) {
        synchronized (list) {
            boolean absent = !list.contains(x);
            if (absent)
               list.add(x);
            return absent;
        }
    }
}

不同之處在於,它使用與列表中其他方法相同的鎖,而不是其他方法。

此代碼不是線程安全的,僅因為list是公共的。

如果列表實例是私有的,並且在其他地方均未引用,則此代碼是線程安全的。 否則它不是線程安全的,因為多個線程可以同時操作該列表。

如果未在其他位置引用該列表,則無需通過collections類將其聲明為同步列表,只要所有列表操作都是通過同步方法進行的,並且對該列表的引用永遠不會返回任何內容。

當您將一個方法標記為同步時,所有調用該方法的線程都將與定義該方法的對象實例同步。這就是為什么如果未在其他位置引用ListHelper內部列表實例,並且所有方法都已同步,則您的代碼將是線程安全的。

線程安全的一個主要組成部分不僅僅是相互排斥。 完全有可能完成對象狀態的原子更新,即進行狀態轉換,使對象保持其不變式處於有效狀態,但是如果對象的引用仍然發布為不可信或不完整,則仍然使對象容易受到攻擊調試的客戶端。

在示例中,您發布:

public synchronized boolean putIfAbsent(E x) {
    boolean absent = !list.contains(x);
    if (absent)
        list.add(x);
    return absent;
}

正如WM所指出的那樣,該代碼是線程安全的。 但是我們不能保證x本身以及其他代碼仍然可以引用的位置。 如果確實存在此類引用,則另一個線程可以修改列表中的相應元素,從而使您無法保護列表中對象的不變性。

如果您從您不信任或不知道的客戶代碼中接受此列表中的元素,那么一個好的做法是制作x的防御副本,然后將其添加到列表中。 同樣,如果要將對象從列表返回到其他客戶端代碼,請進行防御性復制並返回,這將有助於確保列表保持線程安全。

此外,列表應完全封裝在類中。 通過公開,客戶端代碼可以在任何地方自由訪問元素,並且使您無法保護列表中對象的狀態。

暫無
暫無

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

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