Java 如果不存在,是否尝试实现Put的非线程安全?

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

在Java并发实践的第四章中有一个代码片段

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()方法上添加了额外的同步。

以供将来参考。始终将锁定的对象设为私有对象,这样在您不知道的情况下,任何外部代码都无法锁定它们。