4

假设我有以下类定义,当一个线程想要为多个(可能)等待的线程设置一个时:

public class A {
    private int a;
    private CountDownLatch gate;

    public A(int a) {
        a = 1;
        gate = new CountDownLatch(1);
    }

    public int getA() {
        latch.await();
        return a;
    }

    public void setA(int a) {
        this.a = a;
        gate.countDown();
    }
}

在我看来,a 需要是 volatile 的,但我不确定……有人可以分享一下为什么(如果有的话)需要围绕 getA 进行额外的同步,或者 a 需要是 volatile 的吗?

4

2 回答 2

4

根据javadoc

在计数达到零之前,在从另一个线程中countDown()的对应项成功返回之后调用happens-before 操作之前的线程中的操作。await()

setA因此,如果您只调用一次,则不需要额外的同步。如果您再次调用它,因为计数已经为 0,您将不会得到相同的保证。

如果预期的用途是只调用setA一次,那么如果多次调用它以强制执行该合同,则可以抛出异常(尽管如果没有额外的同步,检查计数并以原子方式为 a 分配新值可能会很棘手)。

如果您很高兴setA可以多次调用,那么您需要额外的同步。

于 2016-01-13T10:53:28.613 回答
3

实际上a不需要是 volatile 因为countDown()加载和存储到 volatilestate变量中,AbstractQueuedSynchronizer其中使用了CountDownLatch. 易失性存储触发了内存屏障(JSR-133 中关于内存屏障等的非常深入的文章)。并且根据 JMM,所有以前的存储(对其他变量)对其他线程都是可见的。
assyliassetA()是对的,只有当你调用一次时才会正确,因为你将latch 构造为new CountDownLatch(1).

于 2016-01-13T11:02:02.497 回答