假设我有以下代码:
final Catalog catalog = createCatalog();
for (int i = 0; i< 100; i++{
new Thread(new CatalogWorker(catalog)).start();
}
“Catalog”是一个对象结构,而createCatalog()和“Catalog”对象结构的方法并没有考虑到并发性。产品目录中有几个非最终的非易失性引用,甚至可能存在可变状态(但是 要处理)
我理解内存模型的方式,这段代码不是线程安全的。有没有简单的方法让它安全? (这个问题的通用版本实际上是关于在线程爆炸成行动之前创建的共享结构的单线程构造)
答案 0 :(得分:5)
不,没有简单的方法可以确保安全。并发使用可变数据类型总是很棘手。在某些情况下,使Catalog
上的每个操作同步(最好是在私有锁上)可能有效,但通常你会发现一个线程实际上想要执行多个操作,而不会冒任何其他线程搞乱事情。
仅仅同步对变量的每次访问都应该足以使Java内存模型问题不那么相关 - 例如,您总是会看到最新的值 - 但更大的问题本身仍然很重要。
Catalog
中的任何不可变状态都应该没有问题:Catalog
的构造和正在启动的新线程之间存在“发生之前”。来自规范的section 17.4.5:
在线程上调用start() 发生在 - 之前的任何行动之前 开始了。
(构造完成发生在调用start()
之前,因此构造发生在启动线程中的任何操作之前。)
答案 1 :(得分:1)
您需要同步每个更改Catalog
状态的方法,以使其成为线程安全的。
public synchronized <return type> method(<parameter list>){
...
}
答案 2 :(得分:1)
假设您处理“非最终的,非易失性引用[和]可变状态”(可能是因为在这些线程运行时实际上没有变异),我相信这是线程安全的。来自the JSR-133 FAQ:
之前发生一次动作 另外,第一个保证是 之前订购并可见 第二。这种排序的规则是 如下:
- 线程中的每个操作都发生在该线程中的每个操作之前 在程序的后期出现 顺序。
- 监视器上的解锁发生在每次后续锁定之前 同一台监视器。
- 在每次后续读取之前发生对volatile字段的写入 那种挥发性的。
- 对线程的start()调用发生在之前的任何操作之前 开始了。
- 线程中的所有操作都在任何其他线程成功之前发生 从该线程上的join()返回。
由于线程是在调用createCatalog之后启动的,因此createCatalog的结果应该对这些线程可见而没有任何问题。它只是在线程上调用start()之后发生的Catalog对象的更改,这会导致麻烦。