不稳定的出版物保证多深?

gst*_*low 8 java concurrency visibility volatile happens-before

众所周知,如果我们有一些对象引用并且此引用具有final字段 - 我们将看到来自final字段的所有可到达字段(至少在构造函数完成时)

例1:

class Foo{
    private final Map map;
     Foo(){
         map = new HashMap();
         map.put(1,"object");
     }

     public void bar(){
       System.out.println(map.get(1));
     }
}
Run Code Online (Sandbox Code Playgroud)

正如我在这种情况下所做的那样,我们保证bar()方法总是输出object因为:
1.我列出了完整的类代码,Foo并且map是final;
2.如果某个线程会看到引用Foo和引用!= null,那么我们保证从最终map引用值可以到达的是实际的.

我也是这么认为的

例2:

class Foo {
    private final Map map;
    private Map nonFinalMap;

    Foo() {
        nonFinalMap = new HashMap();
        nonFinalMap.put(2, "ololo");
        map = new HashMap();
        map.put(1, "object");
    }

    public void bar() {
        System.out.println(map.get(1));
    }

    public void bar2() {
        System.out.println(nonFinalMap.get(2));
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,我们对bar()方法有相同的保证,但尽管在分配之前进行了 分配,但仍bar2可以抛出NullPointerExceptionnonFinalMapmap

我想知道volatile如何:

例3:

class Foo{
        private volatile Map map;
         Foo(){
             map = new HashMap();
             map.put(1,"object");
         }

         public void bar(){
           System.out.println(map.get(1));
         }
    }
Run Code Online (Sandbox Code Playgroud)

据我所知,bar()方法不能扔,NullPoinerException但它可以打印null; (我完全不确定这个方面)

例4:

class Foo {
    private volatile Map map;
    private Map nonVolatileMap;

    Foo() {
        nonVolatileMap= new HashMap();
        nonVolatileMap.put(2, "ololo");
        map = new HashMap();
        map.put(1, "object");
    }

    public void bar() {
        System.out.println(map.get(1));
    }

    public void bar2() {
        System.out.println(nonFinalMap.get(2));
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为这里我们有相同的保证bar()方法也bar2()不能抛出,NullPointerException因为nonVolatileMap赋值写入更高的volatile映射赋值但它可以输出null


在Elliott Frisch发表评论后补充道

通过比赛示例发布:

public class Main {
    private static Foo foo;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                foo = new Foo();
            }
        }).start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                while (foo == null) ; // empty loop

                foo.bar();
            }
        }).start();

    }
}
Run Code Online (Sandbox Code Playgroud)

请将我的评论改为或更正为代码片段.

Ale*_*lev 22

在当前Java内存模型的领域,volatile并不相同final.换句话说,你不能代替finalvolatile,并认为安全施工保证是相同的.值得注意的是,这理论上可以发生:

public class M {
  volatile int x;
  M(int v) { this.x = v; }
  int x() { return x; }
}

// thread 1
m = new M(42);

// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // allowed to print "0"
Run Code Online (Sandbox Code Playgroud)

因此,volatile在构造函数中编写字段并不安全.

直觉:m上面的例子中有一场比赛.通过制造这个领域 并没有消除这场比赛M.x volatile,只会让m自己volatile有所帮助.换句话说,volatile该示例中的修饰符位于错误的位置以使其有用.在安全发布中,您必须具有"写入 - >易失性写入 - >易失性读取,观察易失性写入 - >读取(现在观察易失性写入之前的写入)",而是"易失性写入 - >写入 - >读取 - > volatile read(不遵守volatile写入)".

琐事1:这个属性意味着我们可以volatile在构造函数中更积极地优化-s.这证实了直觉,即未观察到的易失性存储(实际上直到具有非逃逸this完成的构造函数才能观察到)可以放松.

琐事2:这也意味着你无法安全地初始化volatile变量.更换MAtomicInteger上面的例子,你有一个奇特的现实生活中的行为!new AtomicInteger(42)在一个线程中调用,不安全地发布实例,并get()在另一个线程中执行 - 你保证观察到42吗?如上所述,JMM说"不".Java内存模型的较新版本力求保证所有初始化的安全构建,以捕获此案例.而许多非重要的非x86端口已经加强了这个安全性.

琐事3: Doug Lea:"这个finalvs volatile问题导致了java.util.concurrent中的一些曲折结构,允许0作为基本/默认值,在它自然不会的情况下.这个规则很糟糕,应该改变."

也就是说,这个例子可以变得更加狡猾:

public class C {
  int v;
  C(int v) { this.x = v; }
  int x() { return x; }    
}

public class M {
  volatile C c;
  M(int v) { this.c = new C(v); }
  int x() { 
    while (c == null); // wait!
    return c.x();
  }
}

// thread 1
m = new M(42);

// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // always prints "42"
Run Code Online (Sandbox Code Playgroud)

如果 volatile读取之后存在传递读取volatile字段,则观察由构造函数中的volatile写入的值,通常的安全发布规则启动.