非线程安全尝试实施Put-if-absent?

时间:2014-04-15 14:10:58

标签: java multithreading concurrency

Java Concurrency in Practice

的第4章中有一个代码片段
public 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;
    }
}

它说使用不同的锁是线程安全的, putIfAbsent相对于List上的其他操作不是原子的。 但我认为&#34;同步&#34;防止多线程进入putIfAbsent,如果还有其他方法在List上执行其他操作,则关键字synchronized也应该作为方法atttribute。那么按照这种方式,它应该是线程安全的吗?在什么情况下&#34;它不是原子&#34;?

4 个答案:

答案 0 :(得分:2)

  

putIfAbsent相对于List上的其他操作不是原子的。但我认为“同步”阻止多线程进入putIfAbsent

这是事实,但无法保证线程有其他方式访问listlist字段为public(这总是一个坏主意),这意味着其他线程可以直接调用list上的方法。要正确保护列表,您应该private并将add(...)和其他方法添加到ListHelper synchronized以完全控制所有访问synchronized-list。

// we are synchronizing the list so no reason to use Collections.synchronizedList
private List<E> list = new ArrayList<E>();
...
public synchronized boolean add(E e) {
    return list.add(e);
}

如果列表为private并且所有方法都已同步以访问列表,那么您可以删除Collections.synchronizedList(...),因为您自己正在同步它。

  

如果有其他方法在List上执行其他操作,则关键字synchronized也应该作为方法atttribute。那么按照这种方式,它应该是线程安全的吗?

不确定我是否完全解析了这部分问题。但是,如果您将list设为private并添加其他方法来访问列表中的所有synchronized,那么您就是正确的。

  

在什么情况下“它不是原子的”?

putIfAbsent(...)不是原子的,因为有多次调用synchronized-list。如果列表上有多个线程正在运行,那么另一个线程可以在list.add(...)之间putIfAbsent(...)之间调用list.contains(x),然后调用list.add(x)Collections.synchronizedList(...)保护列表不受多个线程的损坏,但是当有多个list方法调用可能与来自其他线程的调用交错时,它无法防止竞争条件。

答案 1 :(得分:0)

任何修改列表的非同步方法都可能在list.contains()返回false之后但在添加元素之前引入缺少的元素。

将此图片视为两个主题:

boolean absent = !list.contains(x); // Returns true
-> list.add(theSameElementAsX);     // Another thread
if(absent)   // absent is true, but the list has been modified!
   list.add(x);
return absent;

这可以通过以下方法完成:

public void add(E e) {
    list.add(e);
}

如果方法是同步的,那么就没有问题了,因为add方法在putIfAbsent()完全完成之前无法运行。

正确的修正包括将列表设为私有,并确保其上的复合操作正确同步(即在类或列表本身上)。

答案 2 :(得分:0)

Collections.synchronizedList()创建了一个集合,可以为私有互斥锁添加每个单个方法的同步。对于示例中使用的单参数工厂,此互斥锁为list this,或者在使用双参数工厂时可以提供。这就是为什么我们需要一个外部锁来使后续的contains()add()调用成为原子。

如果列表可用直接,而不是通过ListHelper,则此代码会被破坏,因为在这种情况下,访问将受到不同锁的保护。为了防止这种情况,可以使list为私有以防止直接访问,并将所有必要的API包含在ListHelperthis ListHelper中声明的相同互斥锁上。本身。

答案 3 :(得分:0)

线程安全无法组合!想象一下完全由“线程安全”类构建的程序。程序本身“线程安全吗?”不必要。这取决于程序对这些类的作用。

synchronizedList包装器使List的每个单独方法“线程安全”。那是什么意思?这意味着在多线程环境中调用时,这些包装方法都不会破坏列表的内部结构

这不保护任何给定程序使用列表的方式。在示例代码中,列表似乎用作集合的实现:程序不允许同一对象多次出现在列表中。但是,synchronizedList包装器中没有任何内容可以强制执行该特定保证,因为该保证与列表的内部结构无关。该列表可以作为列表完全有效,但不能作为集合有效。

这就是putIfAbsent()方法的额外同步的原因。