首页 > 解决方案 > Java 中的同步构造是否在内部(并且以某种方式)使用硬件原语 CAS 操作?

问题描述

我很难理解每个 java 对象上存在的语句和相关方法的硬件支持是什么。synchronizednotify()notifyAll()wait()

我已经阅读并知道如何使用这种结构,但我一直认为它们直接映射到硬件原语。

当我进一步研究有关并发的书籍时,我只阅读了硬件直接提供的比较和交换 (CAS) 操作。

似乎这种结构是由 JVM 本身创建/维护的。如果我的阅读是正确的,那么每个对象都包含一些状态,其中包含有关访问它的线程的信息。这用于定义该对象的监视器并协调多个线程对该对象的访问。
但如果是这样的话,这个状态本身是如何从并发访问中管理的?它肯定必须得到管理,对吗?是 CAS 吗?

如果是 CAS,则意味着只有一种真正的同步形式,即 CAS。其他都是衍生品。既然原子变量(即 CAS)在性能方面更好并且无需等待,为什么还要开发具有相关、、、、方法synchronized的监视器构造?notify()notifyAll()wait()

我知道用户类的原子变量只出现在 Java 5.0 左右之后,但在此之前 Java 已经有了这个监视器/内在锁。它们是如何实施的?

标签: javascalaconcurrencysynchronizationjava-memory-model

解决方案


安顿在孩子们身上,这将是一个漫长的过程。

首先避免讨论CAS(比较A nd S wap)这不是同步机制。这是一个原子操作,允许我们更新主内存中的值,同时测试该值是否没有改变(或者我们期望它是什么)。不涉及锁定。尽管它们被一些同步原语(信号量、互斥锁)使用。让我们看看下面的例子:

              a = 1;
--------------------------------
     Thread 1   |  Thread 2 
    b = 1 + a   |   b = 2 + a
 cas(*a, 1, b ) | cas(*a, 1, b )

现在其中一个 CAS-es 将失败,我的意思是它将返回 false。另一个将返回 true,并且指针 *a 表示的值将更新为新值。如果我们不使用 CAS 而是只更新值,如下所示:

              a = 1;
--------------------------------
     Thread 1   |  Thread 2 
    b = 1 + a   |   b = 2 + a
      a = b     |     a = b

在这个计算结束时,a 可能是 2 或 3,两个线程都会很高兴地完成,不知道 a 中保存了什么值。这就是所谓的数据竞争,而 CAS 是解决这个问题的一种方法。

CAS 的存在使我们能够编写一些无锁算法(不需要同步),例如 java.util.concurrent 包中的集合,不需要同步,可以并发访问。

现在我提到CAS是用来实现同步的。这就是为什么获取锁和执行 CAS 的成本几乎相同的原因(如果没有争用!!!!)并且在那个发送中,您可以获得同步关键字的硬件支持。

synchronized(this){ 
     n = n + 1; 
}

AtomicLong al = new AtomicLong();
al.updateAndGet( n -> n + 1)

使用 CAS 与同步时您可能会受到的性能影响来自当您的 CAS 失败时,您可以重试,而使用同步可能会导致线程进入操作系统。进入上下文切换的兔子洞(可能会发生也可能不会发生:),具体取决于操作系统)。

现在为notify(), notifyAll() and wait(). 直接调用作为操作系统一部分的线程调度程序。调度程序有两个队列Wait QueueRun Queue。当您在线程上调用等待时,该线程将被放置在 wq 中并坐在那里,直到它得到通知并放置在 rq 中以便尽快执行。

在 Java 中有两种基本的线程同步方式,一种是通过 (wait(), notify()) 称为协作,另一种是通过锁称为互斥(mutex)。这通常是平行的轨道来做一次思考。

现在我不知道在 Java 5 之前同步是如何完成的。但是现在您有两种使用对象进行同步的方法(一种可能是旧的,另一种可能是新的)。

  1. 偏向锁。线程 id 放在对象头中,然后当同一个特定线程想要锁定时,解锁该对象,该操作不会花费我们任何费用。这就是为什么如果我们的应用程序有很多非竞争锁,这会给我们带来显着的性能提升。因为我们可以避免第二条路径:

  2. (这可能是旧的)使用monitorenter/monitorexit. 这是字节码指令。这被放置在synchronize {...}语句的进入和退出上。这是对象身份变得相关的地方。因为它成为锁定信息的一部分。

好的,就是它。我知道我没有完全回答这个问题。这个题目太复杂太难了。“Java 语言规范”中的第 17 章标题为:“Java 内存模型”可能是唯一一个普通程序员无法阅读的内容(也许动态调度也属于该类别:))。我希望至少你能用谷歌搜索正确的单词。

几个链接: https ://www.artima.com/insidejvm/ed2/threadsynchP.html (monitorenter/monitorexit,解释)

https://www.ibm.com/developerworks/library/j-jtp10185/index.html(如何在jvm内部优化锁)


推荐阅读