Java 在构造函数中进行同步,使其在

Java 在构造函数中进行同步,使其在,java,multithreading,concurrency,synchronization,Java,Multithreading,Concurrency,Synchronization,我有一个关于如何通过Java内存模型保证对象是线程安全的问题 我读过很多书,说在构造函数中编写同步作用域没有意义,但为什么没有呢?是的,只要构造中的对象不在线程之间共享(不应该共享),除了构造线程之外,没有其他线程可以访问任何同步(this){…},因此不需要在构造函数中设置该作用域来排除它们。但是同步作用域不仅仅是为了排除;它们还用于创建发生在关系之前的事件 下面是一个示例代码来说明我的观点 public class Counter{ private int count;

我有一个关于如何通过Java内存模型保证对象是线程安全的问题

我读过很多书,说在构造函数中编写同步作用域没有意义,但为什么没有呢?是的,只要构造中的对象不在线程之间共享(不应该共享),除了构造线程之外,没有其他线程可以访问任何同步(this){…},因此不需要在构造函数中设置该作用域来排除它们。但是同步作用域不仅仅是为了排除;它们还用于创建发生在关系之前的事件

下面是一个示例代码来说明我的观点

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创建一个计数器对象,另一个线程t1使用它。如果构造函数中有synchronized语句,显然可以保证它是线程安全的。(因为同步作用域中的所有动作都有一个“先发生后发生”的关系。)但如果没有,即没有同步语句,Java内存模型是否仍能保证t1可以看到计数t0的初始化写入?我想不是。这就像f.y可以在中的示例代码17.5-1中看到0一样。与JSL.17.5-1的情况不同,现在第二个线程只从同步方法访问字段,但我认为同步语句在这种情况下没有保证效果。(他们不会通过t0创建与任何操作的任何“发生在之前”关系。)有人说,关于构造函数末尾的“发生在边缘之前”的规则保证了这一点,但该规则似乎只是说构造函数发生在finalize()之前

那么我是否应该在构造函数中编写synchronized语句以使对象线程安全?或者,关于Java内存模型,是否有一些规则或逻辑我已经错过了,但实际上没有必要这样做?如果我是真的,那么即使是openjdk的哈希表(尽管我知道它已经过时)似乎也不是线程安全的


还是我对线程安全的定义和并发策略的理解有误?如果我以线程安全的方式(例如通过易失性变量)将计数器对象从t0传输到t1,那么似乎没有问题。(在这种情况下,t0的构造发生在volatile write之前,volatile write发生在t1读取之前,volatile read发生在t1对其执行的所有操作之前。)我是否应该始终在线程之间传输线程安全的对象(但不是不可变的),通过一种导致“发生在之前”关系的方式?

如果对象被安全发布(例如,将其实例化为
someVolatileField=new Foo()
),则不需要构造函数中的同步。如果不需要,则构造函数中的同步是不够的

关于这一点,java并发兴趣列表上有一个较长的讨论;我将在这里提供摘要(完整披露:我开始了讨论,并参与了整个讨论)

记住,before边只在一个线程释放锁和后续线程获取锁之间应用。因此,假设您有:

someNonVolatileField = new Foo();
这里有三组重要的行动:

  • 正在分配的对象,其所有字段均设置为0/null
  • 正在运行的构造函数,其中包括获取和释放对象的监视器
  • 对象的引用被分配给
    someNonVolatileField
  • 假设另一个线程随后使用该引用,并调用一个
    synchronized doFoo()
    方法。现在我们再添加两个操作:

  • 读取
    someNonVolatileField
    参考
  • 调用
    doFoo()
    ,其中包括获取和释放对象的监视器
  • 由于发布到某个非易失性字段是不安全的,因此系统可以进行大量的重新排序。特别是,允许读取线程按以下顺序查看发生的事情:

  • 正在分配的对象,其所有字段均设置为0/null
  • 对象的引用被分配给
    someNonVolatileField
  • 读取
    someNonVolatileField
    参考
  • 调用
    doFoo()
    ,其中包括获取和释放对象的监视器
  • 正在运行的构造函数,其中包括获取和释放对象的监视器
  • 在本例中,仍然有一个before before,但与您想要的相反。具体地说,对
    doFoo()
    的调用正式发生在构造函数之前

    这确实给您带来了一点好处;这意味着任何同步方法(或块)保证看到构造函数的全部效果,或者没有任何效果;它不会只看到构造函数的一部分。但在实践中,您可能希望保证看到构造函数的效果;这就是您编写构造函数的原因


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

    ”考虑这样一种情况,一个线程t0创建一个计数器对象,另一个线程t1使用它。“在构造函数返回t0之前,t1如何引用
    计数器
    ?安全发布解决了您的问题。@Real怀疑论者假设JVM为1.5+,在第一个线程写入引用和任何后续线程读取引用之间有一个完整的before edge。as