Java 请解释一下这个成语中的种族条件

Java 请解释一下这个成语中的种族条件,java,concurrency,synchronization,race-condition,java.util.concurrent,Java,Concurrency,Synchronization,Race Condition,Java.util.concurrent,考虑以下代码,并假设该列表是一个同步列表 List list = Collections.synchronizedList(new ArrayList()); if(!list.contains(element)){ list.add(element) } 我知道上面的代码片段需要在外部同步(由锁保护),以使其完全线程安全。这里的竞争条件是什么 好吧,假设两个线程执行检查并都输入条件语句。然后两者都将向列表中添加相同的元素,这不是预期的行为。不是吗?好吧,假设两个线程执行检查,

考虑以下代码,并假设该列表是一个同步列表

List list = Collections.synchronizedList(new ArrayList());

if(!list.contains(element)){
       list.add(element)
}

我知道上面的代码片段需要在外部同步(由锁保护),以使其完全线程安全。这里的竞争条件是什么

好吧,假设两个线程执行检查并都输入条件语句。然后两者都将向列表中添加相同的元素,这不是预期的行为。不是吗?

好吧,假设两个线程执行检查,并且都输入条件语句。然后两者都将向列表中添加相同的元素,这不是预期的行为。不是吗?

在获取列表和重新保存列表(如果保存到文件示例)之间,竞争条件将起作用

示例:文件包含一个

  • 线程1得到一个
  • 线程1将B添加到A
  • 线程2得到一个
  • 线程1将AB保存到文件中
  • 线程2将C添加到
  • 线程2将AC保存到文件

读取文件将包含AC,并且工作线程1 did丢失

在获取列表和重新保存列表(如果保存到文件示例)之间,竞争条件将起作用

示例:文件包含一个

  • 线程1得到一个
  • 线程1将B添加到A
  • 线程2得到一个
  • 线程1将AB保存到文件中
  • 线程2将C添加到
  • 线程2将AC保存到文件

读取文件将包含AC,并且工作线程1所做的工作将丢失。

依我看,如果<代码>列表,则会出现竞态条件。contains(元素)生成false,另一个线程将在之后但在调用添加到第一个线程之前添加元素。

依我看,如果<代码>列表,则会出现竞态条件。contains(元素)产生false,另一个线程将在此之后但在第一个线程中调用add之前添加元素。

实际上有很多元素。在计算
包含时,列表可能会发生变化,当您到达
添加时,可能有人添加了该元素,您再次添加该元素。此外,如果没有同步,整个过程可能会以奇怪的方式崩溃,因为其他线程的写操作可能会被您的线程部分地、无序地或根本没有观察到


如果
contains
add
本身是原子的(同步的),那么对
contains
add
的调用之间至少会有一个定义明确的竞争,实际上竞争很多。在计算
包含时,列表可能会发生变化,当您到达
添加时,可能有人添加了该元素,您再次添加该元素。此外,如果没有同步,整个过程可能会以奇怪的方式崩溃,因为其他线程的写操作可能会被您的线程部分地、无序地或根本没有观察到


如果
contains
add
本身是原子的(同步的),那么对
contains
add
的调用之间至少会有一个定义明确的竞争假设您有两个线程

A: if(!list.contains(element)){ // false
B: if(!list.contains(element)){ // false
A:     list.add(element) // add once
B:     list.add(element) // add a second time.
当然,简单的解决方法是使用集合。e、 g

Set<E> set = Collections.newSetFromMap(new ConcurrentHashMap<E, Boolean>());

set.add(element);
Set Set=Collections.newSetFromMap(新的ConcurrentHashMap());
集合。添加(元素);

假设您有两个线程

A: if(!list.contains(element)){ // false
B: if(!list.contains(element)){ // false
A:     list.add(element) // add once
B:     list.add(element) // add a second time.
当然,简单的解决方法是使用集合。e、 g

Set<E> set = Collections.newSetFromMap(new ConcurrentHashMap<E, Boolean>());

set.add(element);
Set Set=Collections.newSetFromMap(新的ConcurrentHashMap());
集合。添加(元素);


它们是原子的,因为列表是同步的。我明白了。是的,那么比赛就如最后一段所述。另一个线程可能已经添加了您的元素,而您的线程处于已计算
包含
且尚未输入
add
的状态。通过此示例,您可以看到同步单个列表操作实际上会产生反作用。是的,我想如果对它的所有调用都是外部同步的,就不会使用同步的集合。应该同步对象上的所有读/写方法,以防止我在回答中解释的问题。如果您正在保存到文件,或者正在读取/写入内存(编辑数组),这是正确的。它们是原子的,因为列表是同步的。我明白了。是的,那么比赛就如最后一段所述。另一个线程可能已经添加了您的元素,而您的线程处于已计算
包含
且尚未输入
add
的状态。通过此示例,您可以看到同步单个列表操作实际上会产生反作用。是的,我想如果对它的所有调用都是外部同步的,就不会使用同步的集合。应该同步对象上的所有读/写方法,以防止我在回答中解释的问题。如果您正在保存到文件,或者正在读取/写入内存(编辑数组),这是正确的。如果它们在不同的线程上运行,那么“元素”在不同的线程中不是会不同吗?您不会向程序中的每个线程添加相同的元素,如果添加了,线程安全将无法纠正此问题。如果元素必然不同,则不会出现此问题,但这是一个相当奇怪的要求。请注意,如果A.equals(B)=true,不同的对象实例A和B可能仍然被认为是相同的。@MattWestlake我们当然不能假设任何这样的事情,事实上,在大多数情况下假设它是不好的,因为事实可能在将来发生变化并暴露错误。如果不变量是列表不包含重复项,则必须显式强制执行。如果A和B相同,则线程安全只会确保添加两次记录(一次来自A,一次来自B)。竞赛条件仍然是A.read B.read A.write B.write仅等于B写的内容。@MattWestlake,这取决于你所说的“线程安全”是什么意思。正如问题的作者正确指出的那样,即使使用同步的集合,这种竞争条件也是可能的