繁体   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