加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

java – 构造函数中的同步使其发生在之前

发布时间:2020-12-15 04:35:52 所属栏目:Java 来源:网络整理
导读:我有一个关于如何通过 Java内存模型确保对象保证是线程安全的问题. 我已经阅读了很多内容,说在构造函数中编写同步作用域没有意义,但为什么不呢?是的,确实只要构造中的对象不在线程之间共享(它不应该是这样),除了构造对象之外的任何线程都不能到达任何synchr
我有一个关于如何通过 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++;
    }
}

考虑一下线程t0创建Counter对象而另一个线程t1使用它的情况.如果构造函数中有synchronized语句,那么显然可以保证它是线程安全的. (因为同步作用域中的所有操作都具有相互之间发生的关系.)但如果不是,即没有同步语句,Java内存模型是否仍然保证t1可以看到t0的初始化写入计数?我想不是.就像fy在JLS.17.5中的示例代码17.5-1中看到0一样.与JSL.17.5-1的情况不同,现在第二个线程仅从同步方法访问该字段,但我认为同步语句在这个情况. (他们不会在t0之前创建任何与任何动作发生的关系.)有人说在构造函数结束时关于发生前的边缘的规则保证了它,但规则似乎只是说构造函数发生了 – 在finalize()之前.

那么我应该在构造函数中编写synchronized语句以使对象线程安全吗?或者是否有一些关于我错过的Java内存模型的规则或逻辑,实际上不需要它?如果我是真的,即使是openjdk的Hashtable(虽然我知道它已经过时)似乎不是线程安全的.

或者我是错误的关于线程安全的定义和并发策略?如果我通过线程安全的方式将Counter对象从t0传输到t1,例如通过一个易变的变量,似乎没有问题. (在这种情况下,t0的构造发生在易失性写入之前,这发生在t1的易失性读取之前,发生在t1所做的一切之前.)我应该总是传递线程安全对象(但不是永久不变的)通过一种导致之前发生关系的方式在线程中?

解决方法

如果对象被安全发布(例如,通过将其实例化为someVolatileField = new Foo()),那么您不需要在构造函数中进行同步.如果不是,那么构造函数中的同步是不够的.

关于这个问题,关于java并发利益列表a few years back的讨论有点冗长;我将在这里提供摘要. (完全披露:我开始讨论,并参与其中.)

请记住,before-before edge仅适用于释放锁的一个线程和获取它的后续线程.所以,假设你有:

someNonVolatileField = new Foo();

这里有三组重要的行动:

>正在分配的对象,其所有字段都设置为0 / null
>构造函数正在运行,其中包括对象监视器的获取和释放
>将对象的引用分配给someNonVolatileField

假设另一个线程使用引用,并调用同步的doFoo()方法.现在我们再添加两个动作:

>阅读someNonVolatileField参考
>调用doFoo(),其中包括对象监视器的获取和释放

由于发布到someNonVolatileField并不安全,因此系统可以进行大量重新排序.特别是,允许??读取线程按以下顺序查看事件:

>正在分配的对象,其所有字段都设置为0 / null
>将对象的引用分配给someNonVolatileField
>阅读someNonVolatileField参考
>调用doFoo(),其中包括对象监视器的获取和释放
>构造函数正在运行,其中包括对象监视器的获取和释放

在这种情况下,仍有一个发生在前的边缘,但反过来你想要的.具体来说,对doFoo()的调用正式发生在构造函数之前.

这确实会给你带来一些收获;这意味着任何同步的方法(或块)都可以保证看到构造函数的完整效果,或者看不到这些效果;它不会只看到构造函数的一部分.但实际上,您可能希望保证看到构造函数的效果;毕竟,这就是你编写构造函数的原因.

你可以通过让doFoo()不同步来解决这个问题,而是设置一些旋转循环,等待一个表示构造函数已经运行的标志,然后是一个手动同步(this)块.但是当你达到这种复杂程度时,最好只说“这个对象是线程安全的,假设它的初始发布是安全的”.这是大多数可变类的事实上的假设,它们将自己称为线程安全的;不可变的可以使用final字段,即使面对不安全的发布,它也是线程安全的,但不需要显式同步.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读