【问题标题】:Is this synchronized block need?这个同步块需要吗?
【发布时间】:2016-09-01 05:23:23
【问题描述】:

System.out.println(number);上的同步块需要下面的代码吗?

import java.util.concurrent.CountDownLatch;

public class Main {

    private static final Object LOCK = new Object();

    private static long number = 0L;

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch doneSignal = new CountDownLatch(10);

        for (int i = 0; i < 10; i++) {
            Worker worker = new Worker(doneSignal);
            worker.start();
        }

        doneSignal.await();

        synchronized (LOCK) { // Is this synchronized block need?
            System.out.println(number);
        }
    }

    private static class Worker extends Thread {

        private final CountDownLatch doneSignal;

        private Worker(CountDownLatch doneSignal) {
            this.doneSignal = doneSignal;
        }

        @Override
        public void run() {
            synchronized (LOCK) {
                number += 1;
            }
            doneSignal.countDown();
        }

    }
}

我认为这是必要的,因为有可能读取缓存的值。

但有人说:

没必要。
因为当主线程读取变量number时,所有的工作线程都完成了变量number内存中的写操作。

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    doneSignal.await() 是一个阻塞调用,所以你的main() 只会在你的所有Worker 线程都调用doneSignal.countDown() 时继续,使其达到0,这就是await() 方法返回的原因。

    System.out.println() 之前添加synchronized 块是没有意义的,此时您的所有线程都已完成。

    考虑为number 使用AtomicInteger,而不是同步锁来调用+= 1

    【讨论】:

    • 线程是否完成并不重要,重要的是读者能否看到实际的数值。幸运的是,对 countdown()+await() 保证发生在之前:)
    • 所有对doneSignal.await() 重要的是CountDownLatch 达到0,number 发生的事情根本不重要。如果他完全删除了CountDownLatch,则主线程实际上可能在他的所有Worker 线程完成之前完成,具体取决于调度程序,并打印0 或其他一些数字
    • 我认为qwwdfsad的意思是,如果每个countDown()之前的代码和之后的代码之间没有happens-before await(),您仍然可以看到 number 的值小于 10。例如,如果 await() 通过检查使用旧(Java 5.0 之前)volatile 语义更新的值来工作,它可以可靠地看到所有线程都已完成,但它不能可靠地看到 @987654342 的最后一个值@.
    • 感谢@DimitarDimitrov,我现在明白了 qwwdfsad 所指的内容。然而,这些线程确实调用countDown(),所以“完成”包括信号,这是保证happens-before
    • 没问题@micheleb - 没有人争辩说没有happens-before。我(我假设@qwwdfsad)争论的是 happens-before 边缘是打印结果时不需要同步的主要原因,而不是所有线程都已完成的事实。
    【解决方案2】:

    没必要:

    CountDownLatch doneSignal = new CountDownLatch(10);
    
    for (int i = 0; i < 10; i++) {
      Worker worker = new Worker(doneSignal);
      worker.start();
    }
    doneSignal.await();
    // here the only thread running is the main thread
    

    就在每个线程死掉之前countDownLatch

    @Override
    public void run() {
      synchronized (LOCK) {
        number += 1;
      }
      doneSignal.countDown();
    }
    

    只有当 10 个线程完成他们的工作时, doneSignal.await();线将被超越。

    【讨论】:

      【解决方案3】:

      没有必要,因为您正在等待“完成”信号。以一种方式刷新内存,使等待线程中的所有值都对主线程可见。

      但是您可以轻松地进行测试,如果您看到的值与您预期的最终值不同,请在 run 方法中进行需要数(百万)步且不会被编译器优化的计算那么你的最终值对主线程是不可见的。当然,这里的关键部分是确保计算不会得到优化,因此一个简单的“增量”可能会得到优化。这通常对于在您不确定是否有正确的内存屏障的情况下测试并发很有用,因此以后可能会对您有用。

      【讨论】:

        【解决方案4】:

        System.out.println(number); 周围不需要synchronized,但不是因为PrintWriter.println() 实现是内部同步的,也不是因为在doneSignal.await() 解除阻塞时所有工作线程都已完成。

        synchronized 不是必需的,因为在每次调用 doneSignal.countDown 和完成 doneSignal.await() 之前的所有内容之间都有一个 happens-before 边缘。这保证您将成功看到number 的正确值。

        【讨论】:

          【解决方案5】:

          需要

          没有。

          但是,由于没有(记录在案的)保证不会有任何交错,因此可能找到交错的日志条目。

          System.out.println("ABC");
          System.out.println("123");
          

          可以打印:

          AB1
          23C
          

          值得

          几乎肯定不会。大多数 JVM 将使用锁 open JDK does 实现 println

          边缘案例

          正如@DimitarDimitrov 所建议的,该锁还有一种可能的用途,即确保在访问number 之前跨越内存屏障。如果这就是问题,那么你不需要lock,你需要做的就是让numbervolatile

          private static volatile long number = 0L;
          

          【讨论】:

          • OP 的担忧与看到number 的正确更新值有关。这个问题不能通过println()实现synchronized来解决(除非number的更新是在同一个监视器中完成的,很可能是System.out)。
          • 关于您的volatile 更新 - 如果保留doneSignal.await(),则在OP 的示例中volatile 是完全多余的。在当前形式下,您的答案看起来并不正确(或者至少不清楚它如何应用于原始问题)。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多