【问题标题】:Countdownlatch and further synchronisation倒计时和进一步同步
【发布时间】:2016-01-13 10:44:36
【问题描述】:

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

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 的吗?

【问题讨论】:

    标签: java multithreading synchronization countdownlatch


    【解决方案1】:

    根据the javadoc

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

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

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

    如果您对setA 可以多次调用感到满意,那么您需要额外的同步。

    【讨论】:

    • 非常感谢!这种内存一致性是如何实现的?在我看来,闩锁与 A 类没有任何关系,能够跨多个线程同步 a 的状态...
    • How is this memory consistency achieved? 我在回答中对此进行了描述:)
    • @Bober02 所有读取线程(调用getA)将读取a await 完成(程序顺序)。写入线程(唯一一个调用setA 的线程)正在调用countDown写入a(程序顺序)之后。因为 happens-before 关系是可传递的,javadoc 中给出的保证意味着对a 的写入发生在a 的读取之前。所有这些都假设 setA 只被调用一次。
    • 同意,但这仍然是高级别的。不稳定的写入/障碍更多的是我的小巷
    • @Bober02 我不确定您的期望 - 在这种情况下,使用发生前的关系就足够了。您可以参考处理器屏障指令,但这些不是 Java 内存模型的一部分(即使 JVM 通常使用它们来实现 JMM)。
    【解决方案2】:

    实际上a 不需要是易失性的,因为countDown() 加载并存储到AbstractQueuedSynchronizer 的易失性state 变量中,该变量在CountDownLatch 内部使用。易失性存储触发内存屏障 (great in-depth article about Memory Barriers and etc in JSR-133)。根据 JMM,所有以前的存储(对其他变量)对其他线程都是可见的。
    assylias 是对的,只有当你调用一次setA() 时才会这样,因为你将latch 构造为new CountDownLatch(1)

    【讨论】:

    • 啊,所以在锁存器的状态中有易失性读/写。这反过来又是一个完整的障碍,因此更改会在缓存等之间传播。对吗?
    • 是的,就是这样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多