Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/google-chrome/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么这段代码不是线程安全的,即使在使用同步方法时也是如此?_Java_Thread Safety - Fatal编程技术网

Java 为什么这段代码不是线程安全的,即使在使用同步方法时也是如此?

Java 为什么这段代码不是线程安全的,即使在使用同步方法时也是如此?,java,thread-safety,Java,Thread Safety,为什么这段代码不是线程安全的,即使我们使用的是synchronized方法,因此获得了Helper对象的锁 class ListHelper <E> { public List<E> list = Collections.synchronizedList(new ArrayList<E>()); public synchronized boolean putIfAbsent(E x) { boolean absent = !l

为什么这段代码不是线程安全的,即使我们使用的是synchronized方法,因此获得了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;
    }
}
类ListHelper{
public List List=Collections.synchronizedList(newarrayList());
公共同步布尔putIfAbsent(E x){
布尔缺席=!list.contains(x);
如果(缺席)
增加(x);
缺席返回;
}
}

此代码不是线程安全的,只是因为列表是公共的

如果列表实例是私有的,并且不在其他地方引用,则此代码是线程安全的。否则它不是线程安全的,因为多个线程可能同时操作列表

如果列表没有在其他地方引用,则不需要通过collections类将其声明为同步列表,只要所有列表操作都是通过同步方法进行的,并且对该列表的引用永远不会返回到任何对象


当您将一个方法标记为已同步时,调用该方法的所有线程都将与所述方法中定义的对象实例同步。这就是为什么如果
ListHelper
内部列表实例未在其他地方引用,并且所有方法都已同步,则您的代码将是线程安全的。

此代码不是线程安全的,只是因为列表是公共的

如果列表实例是私有的,并且不在其他地方引用,则此代码是线程安全的。否则它不是线程安全的,因为多个线程可能同时操作列表

如果列表没有在其他地方引用,则不需要通过collections类将其声明为同步列表,只要所有列表操作都是通过同步方法进行的,并且对该列表的引用永远不会返回到任何对象


当您将一个方法标记为已同步时,调用该方法的所有线程都将与所述方法中定义的对象实例同步。这就是为什么如果
ListHelper
内部列表实例未在其他地方引用,并且所有方法都已同步,那么您的代码将是线程安全的。

因为当
包含
返回时列表将解锁,然后在调用
添加
时再次锁定。其他东西可能会在两者之间添加相同的元素

如果您打算只使用helper对象中的列表,则应将其声明为private
;如果您这样做,代码将是线程安全的,只要列表的所有操作都通过helper对象中同步的方法进行。还值得注意的是,在这种情况下,您不需要使用
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;
        }
    }
}
类ListHelper{
public List=Collections.synchronizedList(新的ArrayList());
公共布尔putIfAbsent(E x){
已同步(列表){
布尔缺席=!list.contains(x);
如果(缺席)
增加(x);
缺席返回;
}
}
}

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

因为当
包含返回时列表被解锁,然后在调用
添加时再次锁定。其他东西可能会在两者之间添加相同的元素

如果您打算只使用helper对象中的列表,则应将其声明为private
;如果您这样做,代码将是线程安全的,只要列表的所有操作都通过helper对象中同步的方法进行。还值得注意的是,在这种情况下,您不需要使用
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;
        }
    }
}
类ListHelper{
public List=Collections.synchronizedList(新的ArrayList());
公共布尔putIfAbsent(E x){
已同步(列表){
布尔缺席=!list.contains(x);
如果(缺席)
增加(x);
缺席返回;
}
}
}

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

线程安全的一个主要组成部分不仅仅涉及互斥。很有可能完成对象状态的原子更新,即实现状态转换,使对象处于有效状态且其不变量保持不变,但如果对象的引用仍然发布到不可信或未完成调试的客户端,则仍然使对象易受攻击

在您发布的示例中:

public synchronized boolean putIfAbsent(E x) {
    boolean absent = !list.contains(x);
    if (absent)
        list.add(x);
    return absent;
}
正如W.M.所指出的,代码是线程安全的。但是我们不能保证
x
本身,以及它在哪些地方可能还有其他代码持有的引用。如果确实存在这样的引用,那么另一个线程可以修改列表中相应的元素,从而使您无法保护列表中对象的不变量

如果您从客户机代码中接受您不信任或不知道的元素,一个好的做法是制作x的防御副本,然后将其添加到您的列表中。类似地,如果要将列表中的对象返回给其他客户机代码,则创建一个防御副本并返回,这将有助于确保列表保持线程安全

此外,列表应该完全封装在类中。通过将其公开,任何地方的客户端代码都可以自由访问元素,并使您无法保护列表中对象的状态。

一个主要的com