Jum*_*ans 3 java concurrency multithreading synchronization
我有一个关于如何通过Java内存模型确保对象保证是线程安全的问题.
我已经阅读了很多内容,说在构造函数中编写同步作用域没有意义,但为什么不呢?是的,确实只要构造中的对象不在线程之间共享(它不应该是这样),除了构造对象之外的任何线程都不能到达任何synchronized(this){...},所以不需要在构造函数中创建该范围以排除它们.但同步范围不仅仅是排除; 它们也用于创造先发生过的关系.JLS.17.4
这是一个示例代码,使我的观点清楚.
public class Counter{
private int count;
public Counter(int init_value){
//synchronized(this){
this.count = init_value;
//}
}
public synchronized int getValue(){
return count;
}
public synchronized void addValue(){
count++;
}
}
Run Code Online (Sandbox Code Playgroud)
考虑一下线程t0创建Counter对象而另一个线程t1使用它的情况.如果构造函数中有synchronized语句,那么显然可以保证它是线程安全的.(因为同步作用域中的所有操作都具有相互之间发生的关系.)但是如果不是,即没有同步语句,Java内存模型是否仍然保证t1可以看到t0的初始化写入计数?我想不是.就像fy在JLS.17.5中的示例代码17.5-1中看到0 一样.与JSL.17.5-1的情况不同,现在第二个线程仅从同步方法访问该字段,但我认为在这种情况下,synchronized语句没有保证效果.(他们不会在t0之前创建任何与任何动作发生的关系.)有人说在构造函数结束时关于发生前的边缘的规则保证了它,但规则似乎只是说构造函数发生了 - 在finalize()之前.
那么我应该在构造函数中编写synchronized语句以使对象线程安全吗?或者是否有一些关于我错过的Java内存模型的规则或逻辑,实际上不需要它?如果我是真的,即使是openjdk的Hashtable(虽然我知道它已经过时)似乎不是线程安全的.
或者我是错误的关于线程安全的定义和并发策略?如果我通过线程安全的方式将Counter对象从t0传递到t1,例如通过volatile变量,似乎没有问题.(在这种情况下,t0的构造发生 - 在易失性写入之前发生 - 在t1的易失性读取之前发生,这发生在t1所做的一切之前.)我应该总是传递线程安全的对象(但不是永久不变的)通过一种导致之前发生关系的方式在线程中?
如果安全地发布了对象(例如,通过将其实例化为someVolatileField = new Foo()
),则不需要在构造函数中进行同步.如果不是,那么构造函数中的同步是不够的.
几年前关于java并发利益列表的讨论有点冗长; 我将在这里提供摘要.(完全披露:我开始讨论,并参与其中.)
请记住,before-before edge仅适用于释放锁的一个线程和获取它的后续线程.所以,假设你有:
someNonVolatileField = new Foo();
Run Code Online (Sandbox Code Playgroud)
这里有三组重要的行动:
someNonVolatileField
让我们说另一个线程然后使用引用,并调用一个synchronized doFoo()
方法.现在我们再添加两个动作:
someNonVolatileField
参考资料doFoo()
,包括获取和释放对象的监视器由于发布到someNonVolatileField并不安全,因此系统可以进行大量重新排序.特别是,允许读取线程按以下顺序查看事件:
someNonVolatileField
someNonVolatileField
参考资料doFoo()
,包括获取和释放对象的监视器在这种情况下,仍有一个发生在前的边缘,但反过来你想要的.具体来说,doFoo()
正式发生的调用- 在构造函数之前.
这确实会给你带来一些收获; 这意味着任何同步的方法(或块)都可以保证看到构造函数的完整效果,或者没有看到这些效果; 它不会只看到构造函数的一部分.但实际上,您可能希望保证看到构造函数的效果; 毕竟,这就是你编写构造函数的原因.
你可以通过让doFoo()不同步来解决这个问题,而是设置一些旋转循环,等待一个表示构造函数已经运行的标志,然后是一个手动synchronized(this)
块.但是当你达到这种复杂程度时,最好只说"这个对象是线程安全的,假设它的初始发布是安全的".这是大多数可变类的事实上的假设,它们将自己称为线程安全的; 不可变的可以使用final
字段,即使面对不安全的发布,它也是线程安全的,但不需要显式同步.
归档时间: |
|
查看次数: |
674 次 |
最近记录: |