提问者:小点点

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


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

即使线程1同步了写操作,尽管写操作的影响将被刷新到主内存,但线程2仍然看不到它们,因为读操作来自1级缓存。因此,同步写操作只能防止写操作发生冲突。(Java线程安全的仅写哈希图)

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

第三个网站(对不起,我又找不到了)说,对任何对象的每次更改——它不在乎引用来自哪里——都会在方法离开同步块并建立一个发生前的情况时刷新到内存中。

我的问题是:

>

  • 通过退出同步块真正刷新回内存的是什么?(正如一些网站也说的那样,只有锁被获取的对象才会被冲回。

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

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

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

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

    如果我关于所有内容都将被重新读取和刷新的假设是正确的,那么这是通过在锁定和解锁函数中使用synchronized-block来实现的(这也是非常必要的),对吗?如果它是错误的,这个功能如何实现?

    提前感谢!


  • 共2个答案

    匿名用户

    在关系之前发生的事情是你必须理解的基本事情,因为正式规范是根据这些来操作的。“冲洗”等术语是技术细节,可能有助于您理解它们,或者在最坏的情况下误导您。

    如果一个线程在< code > synchronized(object 1){…} 中执行动作< code>A,随后一个线程在< code > synchronized(object 1){…} 中执行动作< code>B,假设< code>object1引用同一个对象,则在< code>A和< code>B之间存在一个发生前关系,并且这些动作对于访问共享可变数据是安全的(假设没有其他人修改此数据)。

    但这是一种有向关系,即 B 可以安全地访问 A 修改的数据。但是当看到两个同步(object1) { ... } 块时,确定 object1 是同一个对象,你仍然需要知道 A 是在 B 之前执行还是 BA 之前执行,才能知道发生之前关系的方向。对于普通的面向对象代码,这通常是自然的,因为每个操作都将在它找到的对象的任何先前状态上运行。

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

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

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

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

    匿名用户

    没有刷新本身,只是这样想更容易(也更容易绘制);这就是为什么网上有很多资源提到刷新到主存储器(RAM假设),但实际上这种情况并不经常发生。真正发生的是,加载和/或存储缓冲区被消耗到L1缓存(IBM为L2),从那里同步数据取决于高速缓存一致性协议;或者换句话说,缓存足够智能,可以相互通信(通过总线),而不是一直从主存储器获取数据。

    这是一个复杂的主题(免责声明:即使我在这方面做了大量的阅读,当我有时间的时候做了大量的测试,我绝对没有完全理解它),它是关于潜在的编译器/cpu/etc重新排序(程序顺序从来没有被尊重过),它是关于缓冲区的刷新,关于内存障碍,释放/获取语义...我不认为你的问题没有博士报告是可以回答的;这就是为什么在< code>JLS中有更高层称为“发生之前”。

    至少理解了上述的一小部分,你就会明白你的问题(至少前两个)意义不大。

    退出同步块真正刷新回内存的是什么

    可能什么都没有——缓存彼此“对话”以同步数据;我只能想到另外两种情况:当您第一次读取一些数据时,以及当线程死亡时,所有写入的数据都将被刷新到主内存中(但我不确定)。

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

    真的,和上面一句话。

    锁是如何实现这一功能的

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