Java 线程安全/发布

Java 线程安全/发布,java,multithreading,thread-safety,Java,Multithreading,Thread Safety,我一直在阅读《行动中的并发》,有几个问题 public final class ThreeStooges { private final Set<String> stooges = new HashSet<String>(); public ThreeStooges() { stooges.add("Moe"); stooges.add("Larry"); stooges.add("C

我一直在阅读《行动中的并发》,有几个问题

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);  
    }  
}  
书上说这不是线程安全的?为什么?这是什么意思,它没有适当出版

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);  
}  
} 
公共类三通{
private List stooges=new ArrayList();
公共三通({
添加(“Moe”);
stooges.添加(“拉里”);
stooges.添加(“卷曲”);
}  
公共同步的void addStoog(字符串stoog){
添加(stoog);
}
公共布尔getStoog(int索引){
返回stooges.get(索引);
}
公共布尔isStooge(字符串名称){
返回stooges.contains(名称);
}  
} 
这里有线程问题吗?getter上的可见性问题? 如果线程A要添加stoog(“Bobby”),线程B调用getStoog(3),那么最后的stoog会在getter上可见吗

如果多个线程调用isStooge(字符串名)方法会怎么样 同时。会发生什么

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

在本例中,您有多个线程访问相同的数据(变量
stooges
),但没有一个线程修改数据,因此您可以

书上说这不是线程安全的?为什么?这是什么意思,它没有适当出版

但是,在本例中,您为
持有者
分配了一个新值,这是一个写操作。这是一个数据竞争,如果两个线程在没有外部同步的情况下同时调用
initialize
,就会发生不好的事情

术语“published”很可能是指一个线程中所做的更改如何对其他线程可见。虽然我不认为这是一个常见的术语,所以我想这本书应该在某个时候给出这个术语的确切定义

和这个一样。怎么了?如果是多个呢 线程调用assertSanity()

正如您所发布的,代码似乎很好。由于
assertSanity
只读取
n
,因此没有数据竞争


然而,正如@EnnoShioji的答案所指出的,可能存在这样一个问题:线程观察到
n的未初始化值,因此看似微不足道的检查可能会失败。

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

这本书的这一部分(Java并发实践)是关于安全发布的。不可变的对象并不自动意味着它没有并发问题(在本例中是可见性问题)。即使
Holder
的状态在其生存期内没有改变,但如果不同的线程在不满足某些规则的情况下访问它,另一个线程可能会看到正在构造的对象,因此会看到不正确的值

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

这就是书中所说的:

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


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

思考Java并发性的一个简单方法是:

  • 每个线程都保存变量的副本
  • 如果不使用Java同步(volatile、locks、synchronized),线程可能永远不会“获取”变量的新副本,在“set”的情况下,它可能永远不会将值刷新到主变量
在您的示例中,通过使用
synchronized
关键字:
synchronized void addStoog


另一方面,您并没有强迫“getter”获得一份新的
stooges
,这将导致读取不一致的数据。

你们两个都帮了大忙,但我不能同时选中这两个。我也不能向上投票,因为我只有不到15个代表+1,谢谢你解释第三个例子。这里有一个后续问题:如果调用
Holder
的构造函数和调用
assertsenity
成员函数之间存在“before”关系,那么这种竞争还会发生吗?我这样问是因为一般来说,如果在构造函数完成执行和调用成员函数之间存在竞争,通常会遇到更大的问题,因为在执行成员函数时可能没有建立类不变量。我认为在Java 5规范中,内存模型已更改,以允许构造函数在调用其他方法之前完全完成。我不知道我是否100%正确。这里需要专家。@ComicSanms:如果我理解正确,如果有一段关系发生在这场比赛没有发生之前。我猜这种竞争条件只会在您尝试进行字符串#哈希代码类型优化(利用操作的幂等性并依赖于引用赋值的原子性,而不是使用诸如
volatile
之类的工具之前发生)或者换句话说,奇怪的东西时才会对您产生影响。
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);  
}  
}