【问题标题】:Do we need to synchronize writes if we are synchronizing reads?如果我们正在同步读取,我们是否需要同步写入?
【发布时间】:2017-06-29 01:01:35
【问题描述】:

我对同步块毫无疑问。 在我提出问题之前,我想分享另一篇相关帖子Link for Answer to related question 的答案。我从同一个答案中引用Peter Lawrey

  1. 同步可确保您拥有一致的数据视图。这意味着您将读取最新值,其他缓存将获得 最新值。缓存足够聪明,可以通过 特殊巴士(不是 JLS 要求的,但允许)这个 总线意味着它不必触摸主存储器即可获得 一致的观点。

  2. 如果只使用同步,则不需要 volatile。如果您有一个非常简单的操作来同步,那么 Volatile 很有用 会有点矫枉过正。

参考上面我有以下三个问题:

第一季度。假设在多线程应用程序中有一个对象或原始实例字段仅在同步块中读取(写入可能发生在其他一些没有同步的方法中)。同步块也定义在其他一些对象上。 将其声明为 volatile(即使仅在 Synchronized 块中读取)是否有意义

第二季度。 我了解已完成同步的对象的状态值是一致的。 我不确定在同步块中读取的其他对象和原始字段的状态。 假设更改是在没有获得锁的情况下进行的,但读取是通过获得锁来完成的。同步块中所有对象的状态和所有原始字段的值是否始终具有一致的视图。 ?

第三季度。 [更新]无论我们锁定什么,在同步块中读取的所有字段都会从主内存中读取吗? [CKing 回答]

我为上述问题准备了参考代码。

public class Test {
  private SomeClass someObj;
  private boolean isSomeFlag;
  private Object lock = new Object();
  public SomeClass getObject() {
        return someObj;
  }
  public void setObject(SomeClass someObj) {
        this.someObj = someObj;
  }
  public void executeSomeProcess(){
        //some process...
  }
  // synchronized block is on a private someObj lock.
  // inside the lock method does the value of isSomeFlag and state of someObj remain consistent?

  public void someMethod(){
        synchronized (lock) {
              while(isSomeFlag){
                    executeSomeProcess();
              }
              if(someObj.isLogicToBePerformed()){
                    someObj.performSomeLogic();
              }
        }
  }
  // this is method without synchronization.
  public void setSomeFlag(boolean isSomeFlag) {
        this.isSomeFlag = isSomeFlag;
  }
}

【问题讨论】:

  • 我想对这件事说得很好,但这是诚实最有效的事情之一。如果您在谈论您拥有的 JMM 时正在考虑诸如“从内存中获取”之类的事情'不理解它,应该远离并发。但是没有给出的代码是不正确的。
  • @ControlAltDel 不,简单地使isSomeFlag volatile 不会解决此代码的问题。您还在someObj 上进行了数据竞赛,这意味着someMethod() 在访问someObj.... 时可能会看到乱码。 (“在此处添加 volatile”几乎总是危险的建议。)
  • @BrianGoetz 这很顺利。 [我不敢相信我亲眼目睹了这一点;)]
  • @CKing 是的,这有点像唐·克努斯(Don Knuth)在你的算法作业上得到帮助,甚至没有注意到:-)
  • @CKing 我想说实际上说这根本不重要 :-) 虽然在 Wikipedia 上查找他可能会花费 15 分钟,但他是一个非常有趣的角色。

标签: java multithreading heap-memory synchronized-block


【解决方案1】:

您需要了解的第一件事是,链接答案中讨论的场景与您正在谈论的场景之间存在细微差别。您谈到在没有同步的情况下修改值,而所有值都是在链接答案的同步上下文中修改的。考虑到这种理解,让我们来解决您的问题:

第一季度。假设在多线程应用程序中有一个对象或原始实例字段仅在同步块中读取(写入可能发生在其他一些没有同步的方法中)。同步块也定义在其他一些对象上。将其声明为 volatile(即使仅在 Synchronized 块中读取)是否有意义?

是的,将字段声明为volatile 确实有意义。由于写入没有发生在synchronized 上下文中,因此无法保证写入线程会将新更新的值刷新到主内存。因此,读取线程可能仍会看到不一致的值。

假设更改是在没有获得锁的情况下进行的,但读取是通过获得锁来完成的。同步块内所有对象的状态和所有原始字段的值是否始终具有一致的视图。 ?

答案仍然是否定的。道理同上。

底线:在同步上下文之外修改值并不能确保这些值被刷新到主内存。 (因为读取器线程可能在写入器线程之前进入同步块)在synchronized 上下文中读取这些值的线程可能最终仍会读取旧值,即使它们从主内存中获取这些值。


请注意,此问题讨论的是原语,因此了解 Java 为 32 位原语(除 long 和 double 之外的所有原语)提供 Out-of-thin-air 安全也很重要,这意味着您可以放心,您至少会看到一个有效值(如果不一致)。

【讨论】:

  • 好的,但是链接的答案呢,它说:1.同步确保您对数据有一致的看法。这意味着您将读取最新值,而其他缓存将获得最新值。和 2. 如果你只使用同步,你就不需要 volatile。如果您有一个非常简单的操作,而同步操作会过大,那么 Volatile 很有用。它们是不是有些不正确或不完整?
  • 链接答案中讨论的场景与您正在谈论的场景之间存在细微差别。您谈到在没有同步的情况下修改值,而所有值都是在链接答案的同步上下文中修改的。在同步上下文之外修改值并不能确保它们被刷新到主内存。
  • @nits.kk 如果这个答案比其他答案更好地回答了您的直接问题,那么不要忘记投票并接受它。
  • 是的。无论您锁定什么,在同步块中读取的所有字段都将从主内存中读取。也就是说,如果您希望读取线程始终看到写入线程所做的更新,它们都需要锁定同一个对象。我希望这能解答你所有的疑惑?
  • 我看不到编辑,因为它已被社区拒绝。但是是的,你的理解是绝对正确的。
【解决方案2】:

synchronized 所做的只是捕获与其同步的对象的锁。如果锁已经被捕获,它将等待它的释放。它不以任何方式断言该对象的内部字段不会改变。为此,有volatile

【讨论】:

  • 感谢回复,我误用了主存,我已经编辑了问题..
【解决方案3】:

当您在对象监视器A 上同步时,可以保证之后在同一监视器A 上同步的另一个线程将看到第一个线程对any 所做的any 对象。这就是synchronized提供的可见性保证,仅此而已。

volatile 变量保证线程之间的可见性(仅对于变量,易失的HashMap 并不意味着映射的内容将可见),而不管任何同步块。

【讨论】:

  • 感谢您的回答。如果假设已经在“ObjectA”的监视器上进行了同步,并且我们在同步块中读取了“ObjectB”的状态(它正在被某个线程以其他方法修改),它会保持一致吗?
  • 除非在 synchronized(ObjectA) 块中对 ObjectB 进行了修改,否则不会。相同的监视器->可见更改。不同的显示器 -> 不保证。
  • 太棒了..实际上这与我想知道的非常接近。只是为了让我自己清楚一个问题:如果更改是在同一监视器上完成的,那么对所有字段(而不仅仅是完成同步的对象)的更改都是可见的?
  • 您同步的对象监视器是无关紧要的,您可以从代码中看到Object lock。但是,当多个线程在同一个线程上同步时,anyany 对象的更改保证从一个到另一个可见。
  • 谢谢 :) 您能否也将其包含在您的答案中。我会将其标记为已接受。根据我提出的问题,它使答案更加清晰。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-05
  • 1970-01-01
相关资源
最近更新 更多