java - 根据java内存模型,synchronized真正做了什么?

时间:2017-12-31 10:58:40

标签: java concurrency synchronized memory-model

在阅读了一些关于java内存模型和同步的内容之后,出现了一些问题:

  

即使线程1同步写入,然后虽然写入的效果将刷新到主存储器,但线程2仍然看不到它们,因为读取来自1级高速缓存。因此,同步写入仅防止写入时发生冲突。 (Java thread-safe write-only hashmap

     

其次,当synchronized方法退出时,它会自动与同一对象的同步方法的任何后续调用建立一个before-before关系。这可以保证对所有线程都可以看到对象状态的更改。 (https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html

第三个网站(我再也找不到了,抱歉)说,任何对象的每次更改 - 它都不关心引用的来源 - 当方法离开时,将被刷新到内存中同步块并建立发生前情况。

我的问题是:

  1. 通过退出synchronized块真正刷回内存的是什么? (正如一些网站还说只有被锁定的对象才会被冲回来。)

  2. 在这种情况下,在重新启动之前发生什么意味着什么?什么不会在进入块时从内存中重新读取什么?

  3. 锁定如何实现此功能(来自https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Lock.html):

      

    所有Lock实现必须强制执行内置监视器锁提供的相同内存同步语义,如Java™语言规范的第17.4节所述:

         
        

    成功的锁定操作与成功的锁定操作具有相同的内存同步效果。     成功解锁操作具有与成功解锁操作相同的内存同步效果。     不成功的锁定和解锁操作以及重入锁定/解锁操作不需要任何内存同步效果。

      
  4. 如果我认为所有内容都将被重新读取和刷新是正确的,那么这可以通过在锁定和解锁功能中使用synchronized-block来实现(对于大多数情况下也是如此),对吗?如果它错了,怎么能实现这个功能呢?

    提前谢谢!

2 个答案:

答案 0 :(得分:2)

发生在关系之前是你必须要理解的基本事情,因为the formal specification就这些而言。像“刷新”这样的术语是可以帮助您理解它们的技术细节,或者在最坏的情况下误导您。

如果某个帖子在A内执行了synchronized(object1) { … }个操作,后跟在B内执行操作synchronized(object1) { … }的帖子,假设object1指的是同一个对象,AB之间存在在关系之前发生,这些操作在访问共享可变数据时是安全的(假设没有其他人修改此数据)

但这是一种有向关系,即B可以安全地访问由A修改的数据。但是,当看到两个synchronized(object1) { … }块时,确保object1是同一个对象,您仍然需要知道在AB之前是否执行了BA之前执行,以了解在关系之前发生的方向。对于普通的面向对象的代码,这通常是自然而然的,因为每个动作都将对它找到的对象的任何先前状态进行操作。

说到刷新,留下synchronized块导致刷新所有书面数据并进入synchronized块导致重新读取所有可变数据,但没有synchronized的互斥保证在同一个实例中,无法控制哪一个发生在另一个之前。更糟糕的是,您不能使用共享数据来检测情况,因为如果不阻塞其他线程,它仍然会不一致地修改您正在操作的数据。

由于在不同对象上进行同步无法建立有效的发生在之前的关系,因此不需要JVM的优化器来维护全局刷新效果。最值得注意的是,如果Escape Analysis已经证明该对象永远不会被其他线程看到,那么今天的JVM将删除同步。

因此,您可以使用对象上的同步来保护对存储在其他位置的数据的访问,即不在该对象中,但是仍然需要在同一对象实例上进行一致的同步,以便对相同的共享数据进行所有访问,这会使程序复杂化逻辑,与简单地同步包含受保护数据的同一对象相比。

如果线程正在读取和写入相同的volatile变量,那么

Lock变量(如内部volatile使用的变量)也具有全局刷新效果,并使用该值来形成正确的程序逻辑。这比使用synchronized块更棘手,因为没有相互排除代码执行,或者,您可以将其视为仅限于单个读取,写入或cas操作的互斥。

答案 1 :(得分:0)

本身没有 flush ,它更容易以这种方式思考(也更容易绘制);这就是为什么网上有很多资源引用刷新到主内存(RAM假设)的原因,但实际上并不常见。真正发生的是对负载和/或存储缓冲区执行排放到L1缓存(在IBM的情况下为L2),并且它可以通过缓存一致性协议从那里同步数据;或者换句话说,缓存足够聪明,可以相互通信(通过总线),而不是始终从主存储器中获取数据。

这是一个复杂的主题(免责声明:尽管我尝试对此进行大量阅读,但是当我有时间进行大量测试时,我绝对不能完全理解它),它是关于潜在的编译器/ cpu / etc重新排序(程序顺序永远不会被尊重),它关于缓冲区的刷新,内存障碍,释放/获取语义...我不认为你的问题是在没有博士报告的情况下回答;这就是JLS中有更高层的原因 - "发生在"之前。

至少理解上面的一小部分,你会明白你的问题(至少前两个),没有多大意义。

  

退出同步块

后,真正刷回内存的是什么

可能什么都没有 - 缓存"谈话"彼此同步数据;我只能想到另外两种情况:当你第一次读取一些数据和线程死亡时 - 所有写入的数据都会被刷新到主存储器中(但我不确定)。

  

在这种情况下,在重新关系之前发生了什么?什么不会在进入块时从内存中重新读取什么?

真的,与上面相同的句子。

  

锁定如何实现此功能

通常通过引入记忆障碍;就像挥发物一样。