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);
}
}