Java 为什么这段代码不是线程安全的,即使在使用同步方法时也是如此?
为什么这段代码不是线程安全的,即使我们使用的是synchronized方法,因此获得了Helper对象的锁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
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