倒计时锁存和进一步同步

Bob*_*r02 5 java multithreading synchronization countdownlatch

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

\n\n
public class A {\n\xc2\xa0\xc2\xa0\xc2\xa0 private int a;\n\xc2\xa0\xc2\xa0\xc2\xa0 private CountDownLatch gate;\n\n\xc2\xa0\xc2\xa0\xc2\xa0 public A(int a) {\n\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0 a = 1;\n\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0 gate = new CountDownLatch(1);\n\xc2\xa0\xc2\xa0\xc2\xa0 }\n\n\xc2\xa0\xc2\xa0\xc2\xa0 public int getA() {\n\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0 latch.await();\n\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0 return a;\n\xc2\xa0\xc2\xa0\xc2\xa0 }\n\n\xc2\xa0\xc2\xa0\xc2\xa0 public void setA(int a) {\n\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0 this.a = a;\n\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0\xc2\xa0 gate.countDown();\n\xc2\xa0\xc2\xa0\xc2\xa0 }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n

ass*_*ias 5

根据javadoc

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

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

如果预期的用途是仅调用setA一次,则如果多次调用以强制执行该契约,则可能会抛出异常(尽管在没有额外同步的情况下检查计数并原子地为 a 分配新值可能会很棘手)。

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


dez*_*hik 4

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