Java重新排序和易变问题

时间:2014-12-19 11:54:11

标签: java multithreading volatile

我最近遇到了一个有趣的问题:

例如我有A类:

class A {
  int a;
  int b;

  public A() {
    a = 1;
    b = 2;
  }

  public int getA() {return a;}
  public int getB() {return b;}
}

A类必须仅作为单身人士存在; 因此,为了提供对单例的访问,我创建了类Factory:

class Factory {
private A a;
public A getA() {
    if (a == null) {
        synchronized(this) {
            if (a == null) {
                a = new A();
            }
        }
    }
    return a;
}

}

据我了解,根据JVM中的重新排序,如果2个线程同时访问Factory.getA(),则其中一个线程可能会获得部分构造的对象,并且可能导致应用程序崩溃。 / p>

但如果我私有A a; volatile我可以确定每个线程只能访问完全构造的对象吗?

因此,作为结论,如果我将变量x标记为volatile,它是否会影响x.class构造函数的内容?

4 个答案:

答案 0 :(得分:2)

  

但如果我私有A a; volatile我可以确定每个线程只能访问完全构造的对象吗?

考虑线程#1初始化a的情况。工厂互斥锁确保一次只有一个线程可以创建A。所以我们有以下顺序:

  1. 线程#1看到a为空

  2. 线程#1获取互斥锁。

  3. 主题#1见a仍为空。

  4. 线程#1构造一个A实例,用于初始化其变量。

  5. 线程#1将实例引用分配给a

  6. 线程#1释放锁定。

  7. 对于线程#2,看到a处于非空状态(例如,在步骤#1或步骤#3测试中),这意味着线程#1必须已经(至少)第五步。但这意味着

    A.a = 1;                    // thread #1
    

    "之前发生"

    A.b = 2;                    // thread #1
    

    "在"之前发生

    write to Factory.a          // thread #1
    

    "发生在"

    之前
    read from Factory.a         // thread #2
    

    "发生在"

    之前
    access to some field of `A` // thread #2
    

    因为在"之前有一个完整的"从线程#1中的新A的字段初始化到线程#2中的字段访问的关系。这意味着线程#2 保证以查看字段初始化写入的结果...除非有其他干预写入这些字段。

    请注意,"发生在"之前。写入易失性a和后续读取之间的另一个线程是关键。如果a不是易变的,那么在"之前就不会发生完整的"链和因此执行不是"发生 - 在一致之前&#34 ;;即它包含"数据竞赛"。

    我上面使用/遵循的术语和推理来自Java语言规范,Chapter 17.4

答案 1 :(得分:1)

<强>挥发性

Java volatile关键字用于将Java变量标记为“存储在主存储器中”。更确切地说,这意味着,每次读取一个volatile变量都将从计算机的主内存中读取,而不是从CPU缓存中读取,并且每次写入volatile变量都将写入主内存,而不仅仅是CPU缓存。

它与记忆有关。它不会影响构造函数。

来源:点击here

答案 2 :(得分:0)

因为你想让A成为一个单身人士,我的建议就是让A成为一个枚举。
使用枚举:

  • 将解决您的问题,因为您没有部分构造的枚举
  • 并且使用枚举是实现单例模式的最佳方式。

    enum A{
       INSTANCE(1,2);
    
       private int a;
       private int b;
    
       A(int a,int b){
           this.a=a;
           this.b=b;
        }
    
       public int getA(){ return a; }
       public int getB(){ return b; }
    }
    

答案 3 :(得分:0)

规范和实现有两种不同的东西。如果你使a变为volatile,那将解决实际问题,因为JVM会在赋值后插入商店障碍。但是这仍然会破坏规范,因为volatile不像final,并不能保证会发布对象的所有子对象。顺便说一句,在具有强大内存模型的CPU上,很多不良代码都可以正常工作,但这并不意味着你应该这样写。