Java 同步块是否会导致所有写缓存刷新?

Java 同步块是否会导致所有写缓存刷新?,java,multithreading,synchronization,jls,Java,Multithreading,Synchronization,Jls,我感兴趣的是synchronized如何工作,即它如何/何时从本地缓存刷新写操作。假设我有以下代码: class Scratch1 { int counter = 0; Scratch1() throws ExecutionException, InterruptedException { counter += 5; counter += 5; // Does this cause to flush possibly cached value written by

我感兴趣的是
synchronized
如何工作,即它如何/何时从本地缓存刷新写操作。假设我有以下代码:

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 Scratch1{
int计数器=0;
Scratch1()抛出ExecutionException、InterruptedException{
计数器+=5;
计数器+=5;
//这是否会导致刷新可能由主线程写入的缓存值,即使它已锁定
//在完全不相关的对象上,并且写入不会发生在同步块内?
已同步(String.class){}
Executors.newCachedThreadPool().submit(()->{
对于(int i=0;i<1000;i++){
计数器+=5;
}
已同步(整型.class){}
}).get();
系统输出打印项次(计数器);
}
}
第2类{
int计数器=0;
Scratch2()抛出ExecutionException、InterruptedException{
//或者这是刷新写入数据的唯一可能的工作方式。
已同步(String.class){
计数器+=5;
计数器+=5;
}
Executors.newCachedThreadPool().submit(()->{
已同步(Integer.class){
对于(int i=0;i<1000;i++){
计数器+=5;
}
}
}).get();
系统输出打印项次(计数器);
}
}
3类{
易失性整数计数器=0;
Scratch3()抛出ExecutionException、InterruptedException{
计数器+=5;
计数器+=5;
Executors.newCachedThreadPool().submit(()->{
对于(int i=0;i<1000;i++){
计数器+=5;
}
}).get();
系统输出打印项次(计数器);
}
}
我有几个问题:

  • 这三个示例是否具有相同的“线程安全”级别(考虑到第一次写入由一个线程完成,第二次写入在第一次写入之后完成(是吗?)以及由另一个线程完成),即“是否保证打印5010”
  • 在同步块外“操作”或使用非易失性属性(我预计易失性访问速度会慢一些)时是否存在性能差异(至少理论上是这样),但在同步块的情况下是“刷新”仅在穿越同步起点/终点时支付的价格,或者在区块内是否也存在差异
  • 我感兴趣的是synchronized在如何/何时从本地缓存刷新写操作的意义上是如何工作的

    实际上,
    synchronized
    不会刷新本地缓存中的写操作。它的行为就好像它这样做了

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

    它们都提供稍微不同形式的线程安全性。如果其他线程同时访问对象,则它们都不是真正安全的。例如,另一个访问
    计数器
    的线程必须同时持有
    字符串.class
    整数.class
    锁,以确保它在操作期间看不到
    计数器
    。第三个线程使用非原子的增量操作,不过如果没有其他线程尝试修改
    计数器
    ,则是安全的

    在同步块外“操作”或使用非易失性属性(我希望易失性访问速度会慢一些,正如本文所证实的那样)方面是否存在性能差异(至少在理论上),但在同步块的情况下是“刷新”仅在穿越同步起点/终点时支付的价格,或者在区块内是否也存在差异

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


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

    我不确定我是否理解“synchronized不刷新写入”部分。那么冲水是什么呢?我的意思是,我是否可以保证,一旦同步块退出,当前线程在该点之前完成的所有写操作都会刷新到主内存,而不管这些写操作是在块内部还是之前发生的?根据您的评估,性能不会有任何差异,因此可能没有理由这样做(除了一级缩进更少:))@svobol13刷新是指更新线程缓存外部的计数器变量吗?@MatthewKerian刷新是指将线程缓存写入RAM,以便其他线程可以获取新数据。@svobol13不保证写入刷新到主存,实际上,写入不会刷新到主存。这很好,因为主内存非常慢。然而,您的代码的行为就像它们被刷新到主内存一样。在您可能正在使用的系统上,实际上没有“线程缓存”可写入RAM(而且存在的缓存在硬件中的线程之间是一致的)。不要混淆你必须获得的效果和获得这些效果所需的条件。@svobol13是的,没错。想象一个需要简单操作的简单CPU,想象这些操作是如何执行的,这有助于理解这些事情是怎么做的。但在真实的系统中,它更像是黑魔法。要理解何时何地需要特定的东西,而不必理解特定于CPU的暗魔法,想象一个与真实世界中的CPU不太相似的通用简单CPU是很有帮助的。在那个假想的CPU上,在同步块进入和退出时,所有内容都会刷新到主内存。在它们内部,锁本身提供了保护
    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);
      }
    }