1

同期メソッドを使用しているため、ヘルパー オブジェクトのロックを取得しているにもかかわらず、このコードがスレッド セーフではないのはなぜですか?

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;
    }
}
4

3 に答える 3

3

リストが公開されているという理由だけで、このコードはスレッドセーフではありません。

リストのインスタンスがプライベートで、どこからも参照されていない場合、このコードはスレッドセーフです。それ以外の場合、複数のスレッドがリストを同時に操作する可能性があるため、スレッドセーフではありません。

リストが他の場所で参照されていない場合、すべてのリスト操作が同期メソッドを通じて行われ、そのリストへの参照が決して返されない限り、コレクション クラスを通じてそれを同期リストとして宣言する必要はありません。

メソッドを同期とマークすると、そのメソッドを呼び出すすべてのスレッドが、そのメソッドが定義されているオブジェクト インスタンスと同期されます。これが、ListHelper内部リスト インスタンスが他の場所で参照されておらず、すべてのメソッドが同期されている場合、コードがスレッドセーフになる理由です。

于 2013-07-08T18:13:55.057 に答える
1

スレッド セーフの主要な要素は、相互排除だけではありません。オブジェクトの状態のアトミックな更新を完了することは十分に可能です。つまり、不変条件をそのままにしてオブジェクトを有効な状態のままにする状態遷移を実行できますが、その参照がまだ信頼できない、または不完全に公開されている場合は、オブジェクトを脆弱なままにしておくことができます。デバッグされたクライアント。

あなたが投稿した例では:

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

WMが指摘したように、コードはスレッドセーフです。xしかし、それ自体と、他のコードによってまだ参照が保持されている可能性がある場所については保証されていません。そのような参照が存在した場合、別のスレッドがリスト内の対応する要素を変更する可能性があり、リスト内のオブジェクトの不変条件を保護する努力が無効になります。

信頼できない、または知らないクライアント コードからこのリストに要素を受け入れる場合は、x の防御コピーを作成し、それをリストに追加することをお勧めします。同様に、リストから他のクライアント コードにオブジェクトを返す場合は、防御的なコピーを作成して返すことで、リストがスレッド セーフのままであることを保証できます。

さらに、リストはクラスに完全にカプセル化する必要があります。パブリックにすることで、クライアント コードはどこからでも要素に自由にアクセスでき、リスト内のオブジェクトの状態を保護できなくなります。

于 2013-07-08T18:24:22.270 に答える