为什么JVM没有编译"递增一个int变量"作为原子获取和增量操作?

时间:2015-11-24 08:31:26

标签: java multithreading jvm

我已经了解到在Java中增加int变量不是原子操作,但是,我发现CPU支持原子Fetch-and-Increment操作。

所以我的问题是,为什么JVM没有将incrementing a int variable操作编译为CPU支持的原子Fetch-and-Increment操作,这在多线程编程中很有用。

  

早期的处理器具有原子测试和设置,fetch-and-increment或交换指令,足以实现互斥,而互斥可以反过来用于实现更复杂的并发对象。

     

- 实践中的Java并发

7 个答案:

答案 0 :(得分:4)

  

所以我的问题是,为什么JVM没有编译将int变量操作递增到CPU支持的原子Fetch-and-Increment操作,这在多线程编程中很有用。

因为在典型的现代CPU中,原子读 - 修改 - 写操作(例如递增)比相应的非原子操作贵几十倍。并且它没有任何好处 - 代码不能依赖于原子操作,因为它们不能保证是原子的。那么好处是什么呢?

虽然它与你的问题没有直接关系,但是因为很多其他人已经错误地解释了这一点,我将解释原子增量和非原子增量之间的两个差异(在硬件级别) ):

  1. 原子增量不能与同一核心中的某些其他操作重叠。也就是说,它必须在某个特定时间发生。这意味着CPU指令流水线操作通常会受到原子操作的严重负面影响。

  2. 为了防止另一个线程在我们的原子操作(读取和写入之间)中将操作重叠到同一个高速缓存行,在原子操作期间锁定高速缓存行。如果另一个核心尝试从执行原子操作的CPU获取高速缓存行,即使是非原子操作,它也必须等到原子操作完成。 (这曾经是一个总线锁。现代CPU更智能。)

  3. 当然,并不能保证每个CPU都是相同的,但是具有多个内核和流行的Java实现的现代CPU几乎肯定会高度优化多核操作。当然,未来的CPU可能会更好。

    此外,纠正另一个常见的误解:现代多核CPU上的缓存直接通信。他们从不需要通过主存储器来同步CPU(除非在极少数情况下,所需的数据仅在主存储器中,并且由于某种原因无法预取)。如果数据在一个核心的缓存中,它可以使用MESI protocol的变体直接转到另一个核心的缓存。这是一件好事 - 如果核心间同步必须通过RAM,多核CPU的性能会非常差。

答案 1 :(得分:3)

因为Java标准(JLS)不需要它,并且因为它是一项昂贵的操作,只应在需要时使用。

答案 2 :(得分:3)

  

所以我的问题是,为什么JVM不编译递增int变量   操作到CPU的原子Fetch-and-Increment操作   支持,这在多线程编程中很有用。

除了JVM可能需要针对缺少此类本机指令的硬件的明显答案之外,我想解决更为通用的问题,“即使所有目标硬件都支持它,为什么不将每个原始操作都原子化?”

线程安全!=线程效率

每当你在支持它的硬件中使用像fetch-and-add / inc这样的原子操作时,就需要一组可能更昂贵的指令。

有了这样的成本,想象一下使用原子获取和添加来简单地在一个大循环中递增计数器,每次迭代做很轻的工作。这样的介绍可能会大大降低循环性能,使程序速度降低到原始速度的一小部分。

线程效率本质上通常需要大部分代码库缺少线程安全性,如上面的循环计数器示例所示。理想情况下,所有仅由单个线程使用的代码都应该是线程不安全的。它不应该在不需要它的地方支付锁定和同步的成本。

我们远没有能够预测操作是否需要线程安全性/原子性的智能编译器。因此,线程效率通常由程序员暂时掌握,并且线程安全性随之而来。

答案 3 :(得分:0)

因为对于多核处理器,每个核心都有一个单独的程序存储器缓存,或者每个处理器都有一个多处理器系统。为了更快地运行,程序被加载到这个缓存中,因为它运行速度比RAM快很多,但是每个核心执行一个线程到它自己的RAM内存副本(在缓存中),通常对其他线程不可见。

这个记忆当然可以与ram同步,但是这个操作很慢。直接从RAM写入和读取速度很慢,它是多CPU /核心系统的瓶颈,这就是为什么在处理器中有一个缓存内存,为它预加载一块程序,所以它运行得更快(512KB的程序包含通常有很多循环,所以它会执行一段时间,而这时其他内核都是从RAM中提供的,它可以加速整个系统的运行。

使一个操作原子化并对所有线程可见意味着它不能被缓存,因此它需要使用直接RAM内存进行读写(或等效 - 一些特殊缓存),这当然会减慢应用。这就是为什么它不是默认值的原因,因为通常情况下你不需要潜在的慢速同步。

答案 4 :(得分:0)

  

为什么JVM没有编译将int变量操作递增到CPU支持的原子Fetch-and-Increment操作,这在多线程编程中很有用。

我已多次问过这个问题,因为我认为该字段为border-collapse:collapse;时应该这样。我得到的反馈是,由于早期版本的Java没有这样做来修复它现在会破坏向后兼容性。也就是说有些人可能有一个程序在不知不觉中依赖于这种行为。鉴于我觉得它不是非常有用,也不保证增量不会以原子方式表现出来并且99%以上的时间我不会在100%的时间esp时看到问题。 volatile但这只是我的意见。

答案 5 :(得分:-1)

- 我想不是每台可以运行Java的机器都将其作为原子操作。请记住,Java可以在几个不同的平台和许多不同的设备上运行.--

检查评论中的一位同事提到的答案Why is i++ not atomic?

答案 6 :(得分:-1)

由于:

  1. Java编译器不编译为机器代码,编译为字节码,

  2. 除了局部变量之外,JVM字节码中没有原子提取和增量指令。