2

我一直在阅读并发操作并且有几个问题。

public final class ThreeStooges {  

    private final Set<String> stooges = new HashSet<String>();  

    public ThreeStooges() {  
        stooges.add("Moe");  
        stooges.add("Larry");  
        stooges.add("Curly");  
    }  

    public boolean isStooge(String name) {  
        return stooges.contains(name);  
    }  
}  

这本书说,因为这个类是不可变的,所以它是线程安全的,因为没有修改状态(stooges)。我很困惑的是这个。如果多个线程同时调用 isStooge(String name) 方法会怎样。怎么了?

public class HolderObject{  

    private Holder holder;  

    public void initialize() {  
        holder = new Holder(42);  
    }  
}  

书上说这不是线程安全的?为什么?没有正确发布是什么意思?

public class Holder {  
    private int n;  

    public Holder(int n) { this.n = n; }  

    public void assertSanity() {  
        if (n != n)  
            throw new AssertionError("This statement is false.");  
    }  
}  

这个也一样。它出什么问题了?如果多个线程调用assertSanity().

感谢你们

更新

假设 stooges 类更改为以下...

public class ThreeStooges {  

private List<String> stooges = new ArrayList<String>();  

public ThreeStooges() {  
    stooges.add("Moe");  
    stooges.add("Larry");  
    stooges.add("Curly");  
}  

public synchronized void addStoog(String stoog){
         stooges.add(stoog);
}

public boolean getStoog(int index){
   return stooges.get(index);
}

public boolean isStooge(String name) {  
    return stooges.contains(name);  
}  
} 

这里有线程问题吗?吸气剂的可见性问题?如果线程 A 要 addStoog("Bobby") 并且线程 B 调用 getStoog(3),那么最终的 stoog 在 getter 上是否可见?

4

3 回答 3

2

如果多个线程同时调用 isStooge(String name) 方法会怎样。怎么了?

在 Java 的内存模型中,如果两个线程同时访问相同的数据并且其中至少有一个是写入,那么您就会遇到麻烦。这称为冲突访问

在这个例子中,你有多个线程访问相同的数据(stooges变量),但它们都没有修改数据,所以你很好。

书上说这不是线程安全的?为什么?没有正确发布是什么意思?

但是,在这种情况下,您为 分配了一个新值holder,即写入。这是一场数据竞赛,如果两个线程initialize在没有外部同步的情况下同时调用,就会发生不好的事情。

术语“已发布”很可能是指在一个线程中完成的更改如何对其他线程可见。虽然我不承认这是一个常用术语,但我想这本书应该在某个时候给出这个术语的确切定义。

这个也一样。它出什么问题了?如果多个线程调用 assertSanity() 会怎样。

正如您发布的那样,代码看起来不错。由于assertSanity只读取n您没有数据竞争。

但是,正如@EnnoShioji 的回答所指出的那样,可能存在线程观察未初始化值的问题n,因此看似微不足道的检查可能会失败。

于 2013-11-14T08:59:15.703 回答
1

我同意@ComicSansMS 的前两个答案,但我认为他的第三个答案并没有解决本书的原点。

本书的那部分(Java Concurrency in Practice)正在讨论安全发布。一个不可变的对象并不自动意味着它没有并发问题(在这种情况下,可见性问题)。即使它的状态Holder在其生命周期内没有改变,如果它被不同的线程访问而不满足某些规则,另一个线程可能会看到一个对象中间构造,因此看到不正确的值。

因为在构造对象时,字段首先填充为空/零,在第三个示例 ( Holder) 中,线程可以看到n=0然后n=42,因此抛出AssertionError

这就是这本书所说的意思:

更糟糕的是,其他线程可能会看到持有者引用的最新值,但持有者状态的值过时

但是,如果您已声明 int 字段final,则 Holder 对象将成为正式的“不可变对象”,因此 JMM 将保证不会发生这种竞争条件(请参阅有关不可变对象的特殊保证的段落)。本书还介绍了您可以遵循的其他规则,以防止这种情况发生。

于 2013-11-14T09:57:31.980 回答
0

考虑 Java 并发性的一种简单方法如下:

  • 每个线程都持有变量的副本
  • 如果不使用 Java 同步(易失性、锁定、同步),线程可能永远不会“获取”变量的新副本,并且在“设置”的情况下,它可能永远不会将值刷新到主变量。

synchronized在您的示例中,您强制“setter”锁定对象并使用关键字将新值刷新到主变量:synchronized void addStoog

另一方面,您不会强迫“getters”获取 的新副本stooges,这将导致读取不一致的数据。

于 2013-11-14T11:17:48.123 回答