我已经了解到在Java中增加int变量不是原子操作,但是,我发现CPU支持原子Fetch-and-Increment
操作。
所以我的问题是,为什么JVM没有将incrementing a int variable
操作编译为CPU支持的原子Fetch-and-Increment
操作,这在多线程编程中很有用。
早期的处理器具有原子测试和设置,
fetch-and-increment
或交换指令,足以实现互斥,而互斥可以反过来用于实现更复杂的并发对象。- 实践中的Java并发
答案 0 :(得分:4)
所以我的问题是,为什么JVM没有编译将int变量操作递增到CPU支持的原子Fetch-and-Increment操作,这在多线程编程中很有用。
因为在典型的现代CPU中,原子读 - 修改 - 写操作(例如递增)比相应的非原子操作贵几十倍。并且它没有任何好处 - 代码不能依赖于原子操作,因为它们不能保证是原子的。那么好处是什么呢?
虽然它与你的问题没有直接关系,但是因为很多其他人已经错误地解释了这一点,我将解释原子增量和非原子增量之间的两个差异(在硬件级别) ):
原子增量不能与同一核心中的某些其他操作重叠。也就是说,它必须在某个特定时间发生。这意味着CPU指令流水线操作通常会受到原子操作的严重负面影响。
为了防止另一个线程在我们的原子操作(读取和写入之间)中将操作重叠到同一个高速缓存行,在原子操作期间锁定高速缓存行。如果另一个核心尝试从执行原子操作的CPU获取高速缓存行,即使是非原子操作,它也必须等到原子操作完成。 (这曾经是一个总线锁。现代CPU更智能。)
当然,并不能保证每个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)
由于:
Java编译器不编译为机器代码,编译为字节码,
除了局部变量之外,JVM字节码中没有原子提取和增量指令。