为什么即使我们使用同步方法并因此在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时再次被锁定。其他可能在两者之间添加相同的元素。

如果您打算仅使用帮助对象中的列表,则应将其声明为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;
        }
    }
}


不同之处在于,它使用与列表中其他方法相同的锁,而不是其他方法。

09-08 02:51