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;?
答案 0 :(得分:2)
putIfAbsent相对于List上的其他操作不是原子的。但我认为“同步”阻止多线程进入putIfAbsent
这是事实,但无法保证线程有其他方式访问list
。 list
字段为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包含在ListHelper
或this
ListHelper
中声明的相同互斥锁上。本身。
答案 3 :(得分:0)
线程安全无法组合!想象一下完全由“线程安全”类构建的程序。程序本身“线程安全吗?”不必要。这取决于程序对这些类的作用。
synchronizedList包装器使List的每个单独方法“线程安全”。那是什么意思?这意味着在多线程环境中调用时,这些包装方法都不会破坏列表的内部结构。
这不保护任何给定程序使用列表的方式。在示例代码中,列表似乎用作集合的实现:程序不允许同一对象多次出现在列表中。但是,synchronizedList包装器中没有任何内容可以强制执行该特定保证,因为该保证与列表的内部结构无关。该列表可以作为列表完全有效,但不能作为集合有效。
这就是putIfAbsent()方法的额外同步的原因。