Java 如果不存在,是否尝试实现Put的非线程安全?
在Java并发实践的第四章中有一个代码片段Java 如果不存在,是否尝试实现Put的非线程安全?,java,multithreading,concurrency,Java,Multithreading,Concurrency,在Java并发实践的第四章中有一个代码片段 public class ListHelper<E> { public List<E> list = Collections.synchronizedList(new ArrayList<E>()); ... public synchronized boolean putIfAbsent(E x) { boolean absent = !list.contain
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;
}
}
公共类ListHelper{
公开名单=
Collections.synchronizedList(新的ArrayList());
...
公共同步布尔putIfAbsent(E x){
布尔缺席=!list.contains(x);
如果(缺席)
增加(x);
缺席返回;
}
}
它说这对于使用不同的锁是线程安全的,putIfAbsent与列表中的其他操作相比不是原子的。
但是我认为“synchronized”阻止多线程进入putIfAbsent,如果列表中有其他方法执行其他操作,那么关键字synchronized也应该作为方法属性。那么按照这种方式,它应该是线程安全的吗?在什么情况下“它不是原子的”任何修改列表的非同步方法都可能在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);
}
如果同步了该方法,则不会有问题,因为在putIfAbsent()完全完成之前,add方法将无法运行
正确的纠正措施包括将列表设置为私有,并确保其上的复合操作正确同步(即在类或列表本身上)。
集合。synchronizedList()
创建一个集合,该集合为它的每个单个方法在私有互斥体上添加同步。对于示例中使用的一个参数工厂,此互斥锁是列表
此
,或者在使用两个参数工厂时可以提供。这就是为什么我们需要一个外部锁来进行后续的contains()
和add()
调用
如果列表直接可用,而不是通过ListHelper
,则此代码将被破坏,因为在这种情况下,访问将由不同的锁保护。为了防止这种情况,可以将list
设为私有以防止直接访问,并将所有必要的API与同步一起包装在ListHelper
中声明的相同互斥体上,或在ListHelper
本身的上
相对于列表上的其他操作,putIfAbsent不是原子的。但我认为“同步”防止多线程进入putIfAbsent
这是真的,但不能保证线程访问列表时有其他方式。list
字段是public
(这总是个坏主意),这意味着其他线程可以直接调用列表上的方法。要正确保护列表,应将其设置为私有,并将add(…)
和其他方法添加到ListHelper
中,这些方法也是synchronized
,以完全控制对同步列表的所有访问
// 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);
}
//我们正在同步列表,因此没有理由使用集合。synchronizedList
私有列表=新的ArrayList();
...
公共同步布尔加法(E){
返回列表。添加(e);
}
如果列表是private
,并且访问该列表的所有方法都已同步,则您可以删除集合。synchronizedList(…)
,因为您自己正在同步它
如果有其他方法在列表上执行其他操作,则关键字synchronized也应作为方法属性。那么按照这种方式,它应该是线程安全的吗
我不确定我是否完全解析了问题的这一部分。但是,如果您将列表
设置为私有
,并添加其他方法来访问该列表,并且这些方法都是同步的
,则您是正确的
在什么情况下“它不是原子的”
putIfAbsent(…)
不是原子的,因为有多个对同步列表的调用。如果有多个线程在列表上运行,那么另一个线程可能会在时间putIfAbsent(…)
之间调用list.contains(x)
,然后调用list.add(x)
。Collections.synchronizedList(…)
可防止列表被多个线程损坏,但当对列表方法的多个调用可能与来自其他线程的调用交错时,它无法防止争用情况。线程安全不可组合!设想一个完全由“线程安全”类构建的程序。程序本身是“线程安全的”吗?不一定。这取决于程序如何处理这些类
synchronizedList包装器使列表的每个方法都是“线程安全的”。这是什么意思?这意味着,当在多线程环境中调用时,这些包装的方法都不会损坏列表的内部结构
这并不能保护任何给定程序使用列表的方式。在示例代码中,列表似乎用作集合的实现:程序不允许同一对象在列表中出现多次。然而,synchronizedList包装中没有任何东西可以强制执行该特定的保证,因为该保证与列表的内部结构无关。列表可以作为列表完全有效,但作为集合无效
这就是为什么在putIfAbsent()方法上添加了额外的同步。以供将来参考。始终将锁定的对象设为私有对象,这样在您不知道的情况下,任何外部代码都无法锁定它们。