【问题标题】:Does synchronized block cause all write cache to flush?同步块是否会导致所有写入缓存刷新?
【发布时间】:2019-06-17 21:35:55
【问题描述】:

我对@9​​87654323@ 如何/何时从本地缓存刷新写入感兴趣。假设我有以下代码:

class Scratch1 {
  int counter = 0;
  Scratch1() throws ExecutionException, InterruptedException {
    counter += 5;
    counter += 5;
    // Does this cause to flush possibly cached value written by main thread even if it locks
    // on totally unrelated object and the write doesnt happen inside the sync block?
    synchronized (String.class) {}
    Executors.newCachedThreadPool().submit(() -> {
      for (int i = 0; i < 1000; i++) {
        counter += 5;
      }
      synchronized (Integer.class) {}
    }).get();
    System.out.println(counter);
  }
}
class Scratch2 {
  int counter = 0;
  Scratch2() throws ExecutionException, InterruptedException {
    // Or is this only possible working way how flush written data.
    synchronized (String.class) {
      counter += 5;
      counter += 5;
    }
    Executors.newCachedThreadPool().submit(() -> {
      synchronized (Integer.class) {
        for (int i = 0; i < 1000; i++) {
          counter += 5;
        }
      }
    }).get();
    System.out.println(counter);
  }
}
class Scratch3 {
  volatile int counter = 0;
  Scratch3() throws ExecutionException, InterruptedException {
    counter += 5;
    counter += 5;
    Executors.newCachedThreadPool().submit(() -> {
      for (int i = 0; i < 1000; i++) {
        counter += 5;
      }
    }).get();
    System.out.println(counter);
  }
}

我有几个问题:

  1. 所有三个示例是否共享相同的“线程安全”级别(考虑到第一次写入由一个线程完成,第二次写入在第一个(是吗?)和另一个线程之后完成)即“是保证打印 5010”?
  2. 在同步块之外“操作”或使用非易失性属性时是否存在性能差异(至少理论上)(我预计易失性访问会像this post confirms 一样慢)但在同步块的情况下是“刷新”的价格仅在跨越同步开始/结束时支付,还是在块内也有差异?

【问题讨论】:

  • Java 语言规范 (JLS) 中没有“缓存”。缓存是一个实现细节。 JLS 的“Memory Model”部分详细解释了当您的线程共享数据时您应该期待什么。您可以从中得出的一条经验法则是;线程 A 在离开 synchronized 块之前所做的任何事情都保证在线程 B 进入同一对象上的 synchronized 块时对线程 B 可见。

标签: java multithreading synchronization jls


【解决方案1】:

我对同步的工作原理感兴趣,即它如何/何时刷新本地缓存中的写入。

实际上,synchronized 不会刷新本地缓存中的写入。它只是表现得好像它这样做了。

所有三个示例是否共享相同的“线程安全”级别(考虑到第一次写入由一个线程完成,第二次写入在第一个线程之后完成(是吗?)和另一个线程完成)即“是保证打印 10”?

它们都提供略有不同的线程安全形式。如果其他线程同时访问该对象,它们都不是真正安全的。例如,访问counter 的另一个线程必须同时持有String.classInteger.class 锁,以确保它在操作期间看不到counter。第三个使用非原子的增量操作,但如果没有其他线程尝试修改counter,它是安全的。

在同步块之外“操作”或使用非易失性属性是否存在性能差异(至少理论上)(我希望 volatile 访问会更慢,正如这篇文章所证实的那样),但在同步块的情况下是“刷新”的价格只在跨越同步开始/结束时支付,还是在块内也有差异?

没有区别。进入synchronized 块是有代价的,因为必须获取锁并且必须在入口点禁用一些优化。退出街区的成本相似。

在块内部,没有成本,因为程序员提供了安全性,确保他们不允许任何线程修改对象,除非它持有相同的锁并且没有两个线程可以同时持有相同的锁.一般来说,代码甚至可能不知道它是否在一个或多个 synchronized 块内,因为它可能位于调用树的深处。

【讨论】:

  • 我不确定我是否理解“同步不会刷新写入”部分。那么冲洗什么呢?我的意思是我保证一旦退出同步块,当前线程完成的所有写入都会刷新到主内存,无论这些写入发生在块内部还是之前?根据您的解释,性能没有差异,因此可能没有理由这样做(除了少一级缩进:))。
  • @svobol13 刷新是指更新线程缓存之外的计数器变量吗?
  • @MatthewKerian by flushing 我是指将线程缓存写入 RAM,以便其他线程可以获取新数据。
  • @svobol13 您不能保证写入会刷新到主内存,实际上,它们不会刷新到主内存。这很好,因为主内存非常慢。但是,您的代码就像它们被刷新到主内存一样。在您可能正在使用的系统上,确实没有“线程缓存”可以写入 RAM(无论如何,存在的缓存在硬件中的线程之间是一致的)。不要将您必须获得的效果与获得这些效果所需的条件混淆。
  • @svobol13 是的,完全正确。通过想象一个需要简单操作的简单 CPU 并想象执行这些操作,有助于理解这些事情的作用。但在真实系统中,它更像是黑魔法。要了解何时何地需要特定的东西,而不必了解特定于 CPU 的黑魔法,想象一个与现实世界 CPU 不太相似的通用简单 CPU 会很有帮助。在那个假想的 CPU 上,所有内容都会在同步块的进入和退出时刷新到主内存。在它们内部,锁本身提供了保护。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-30
  • 2012-12-10
  • 2019-06-23
  • 2011-01-21
  • 2010-09-15
  • 2010-09-10
相关资源
最近更新 更多