当前位置: 首页 > 面试题库 >

Java比较和交换语义和性能

谭玄天
2023-03-14
问题内容

Java中比较和交换的语义是什么?即,AtomicInteger公正的比较和交换方法是保证不同线程之间对原子整数实例的特定内存位置的有序访问,还是保证对内存中所有位置的有序访问,即,它的行为就像是易失的(内存围栏)。

从文档:

  • weakCompareAndSet原子地读取和有条件地写入变量,但不会在排序之前创建任何事件,因此,对于除的目标以外的任何变量的先前或后续读取和写入不提供任何保证weakCompareAndSet
  • compareAndSet以及所有其他读取和更新操作(例如,getAndIncrement具有读取和写入易失性变量的内存效应)。

从API文档可以明显看出它的compareAndSet作用就像是一个volatile变量。但是,weakCompareAndSet应该只是更改其特定的内存位置。因此,如果该内存位置是单个处理器的高速缓存专用的,weakCompareAndSet则应该比常规内存快得多compareAndSet

我之所以这样问是因为我已经通过运行从1到8的threadnum不同线程对以下方法进行了基准测试(代码是用静态编译的JVM语言Scala编写的,但是其含义和字节码翻译对于在这种情况下是Java的-
这段简短的片段应该很清楚):threadnum``totalwork=1e9

val atomic_cnt = new AtomicInteger(0)
val atomic_tlocal_cnt = new java.lang.ThreadLocal[AtomicInteger] {
  override def initialValue = new AtomicInteger(0)
}

def loop_atomic_tlocal_cas = {
  var i = 0
  val until = totalwork / threadnum
  val acnt = atomic_tlocal_cnt.get
  while (i < until) {
    i += 1
    acnt.compareAndSet(i - 1, i)
  }
  acnt.get + i
}

def loop_atomic_weakcas = {
  var i = 0
  val until = totalwork / threadnum
  val acnt = atomic_cnt
  while (i < until) {
    i += 1
    acnt.weakCompareAndSet(i - 1, i)
  }
  acnt.get + i
}

def loop_atomic_tlocal_weakcas = {
  var i = 0
  val until = totalwork / threadnum
  val acnt = atomic_tlocal_cnt.get
  while (i < until) {
    i += 1
    acnt.weakCompareAndSet(i - 1, i)
  }
  acnt.get + i
}

在具有4个双2.8 GHz内核和2.67 GHz 4核i7处理器的AMD上。JVM是Sun Server Hotspot JVM
1.6。结果表明没有性能差异。

测试名称:loop_atomic_tlocal_cas

  • 线程数:1

运行时间:(显示最后3个)7504.562 7502.817 7504.626(平均= 7415.637分钟= 7147.628最大= 7504.886)

  • 线程数:2

运行时间:(显示最后3个)3751.553 3752.589 3751.519(平均= 3713.5513分钟= 3574.708最大= 3752.949)

  • 线程数:4

运行时间:(显示最后3个)1890.055 1889.813 1890.047(平均= 2065.7207分钟= 1804.652最大= 3755.852)

  • 线程数:8

运行时间:(显示最后3个)960.12 989.453 970.842(平均= 1058.8776分钟= 940.492最大= 1893.127)

测试名称:loop_atomic_weakcas

  • 线程数:1

运行时间:(显示最后3个)7325.425 7057.03 7325.407(平均= 7231.8682分钟= 7057.03最大= 7325.45)

  • 线程数:2

运行时间:(显示最后3个)3663.21 3665.838 3533.406(平均= 3607.2149分钟= 3529.177最大= 3665.838)

  • 线程数:4

运行时间:(显示最后3个)3664.163 1831.979 1835.07(平均= 2014.2086分钟= 1797.997最大值= 3664.163)

  • 线程数:8

运行时间:(显示最后3个)940.504 928.467 921.376(平均= 943.665分钟= 919.985最大= 997.681)

测试名称:loop_atomic_tlocal_weakcas

  • 线程数:1

运行时间:(显示最后3个)7502.876 7502.857 7502.933(平均= 7414.8132分钟= 7145.869最大= 7502.933)

  • 线程数:2

运行时间:(显示最后3个)3752.623 3751.53 3752.434(平均= 3710.1782分钟= 3574.398最大= 3752.623)

  • 线程数:4

运行时间:(显示最后3个)1876.723 1881.069 1876.538(平均= 4110.4221分钟= 1804.62最大= 12467.351)

  • 线程数:8

运行时间:(显示最后3个)959.329 1010.53 969.767(平均= 1072.8444分钟= 959.329最大= 1880.049)

规格:Intel i7四核@ 2.67 GHz

测试名称:loop_atomic_tlocal_cas

  • 线程数:1

运行时间:(显示最后3个)8138.3175 8130.0044 8130.1535(平均= 8119.2888分钟= 8049.6497最大=
8150.1950)

  • 线程数:2

运行时间:(显示最后3个)4067.7399 4067.5403 4068.3747(平均= 4059.6344分钟= 4026.2739最大=
4068.5455)

  • 线程数:4

运行时间:(显示最后3个)2033.4389 2033.2695 2033.2918(平均= 2030.5825分钟= 2017.6880最大=
2035.0352)

测试名称:loop_atomic_weakcas

  • 线程数:1

运行时间:(显示最后3个)8130.5620 8129.9963 8132.3382(平均= 8114.0052分钟= 8042.0742最大=
8132.8542)

  • 线程数:2

运行时间:(显示最后3个)4066.9559 4067.0414 4067.2080(平均= 4086.0608分钟= 4023.6822最大=
4335.1791)

  • 线程数:4

运行时间:(显示最后3个)2034.6084 2169.8127 2034.5625(平均= 2047.7025分钟= 2032.8131最大=
2169.8127)

测试名称:loop_atomic_tlocal_weakcas

  • 线程数:1

运行时间:(显示最后3个)8132.5267 8132.0299 8132.2415(平均= 8114.9328分钟= 8043.3674最大=
8134.0418)

  • 线程数:2

运行时间:(显示最后3个)4066.5924 4066.5797 4066.6519(平均= 4059.1911分钟= 4025.0703最大=
4066.8547)

  • 线程数:4

运行时间:(显示最后3个)2033.2614 2035.5754 2036.9110(平均= 2033.2958分钟= 2023.5082最大=
2038.8750)

虽然上面示例中的线程局部变量有可能以相同的缓存行结尾,但在我看来,常规CAS及其弱版本之间没有可观察到的性能差异。

实际上,这可能意味着比较弱和交换作用就像完全成熟的内存屏障,即好像它是一个可变变量一样。

问题:这个观察正确吗?另外,是否存在已知的体系结构或Java发行版,其弱比较和设置实际上更快?如果不是,首先使用弱CAS有什么好处?


问题答案:

当然,弱比较和交换 可以
充当完整的volatile变量,具体取决于JVM的实现。实际上,如果在某些体系结构上不可能以比普通CAS更高的性能实现弱的CAS,我不会感到惊讶。在这些体系结构上,很可能实现弱CAS与完全CAS完全相同。或者,可能仅仅是因为您的JVM在使弱CAS变得特别快方面没有进行太多优化,因此
当前的 实现只是调用了完整的CAS,因为它实现起来很快,以后的版本会对此进行完善。

JLS只是说一个弱的CAS不会建立 事前 关联,所以根本不能 保证
它引起的修改在其他线程中是可见的。在这种情况下,您所获得的只是保证比较和设置操作是原子的,但不能保证(可能)新值的可见性。这与保证它 不会
被看到是不同的,因此您的测试与此一致。

通常,尝试通过实验避免对与并发相关的行为做出任何结论。有很多变量需要考虑,如果您不遵循JLS保证的正确性,那么您的程序 可能会随时
中断(也许在不同的体系结构上,也许是在稍加提示的更积极的优化下)更改代码的布局,也许是在将来不存在的JVM的未来版本等下)。有 从来没有
一个理由认为你可以逃脱东西是说没有得到保证,因为实验表明,“它的工作原理”。



 类似资料:
  • 我有一个字符串和一个int,让我们说:and。查看它们是否相同的最快方法是什么,or(或者有更快的方法吗?)? 这是Integer的源代码。parseInt和String。等于

  • 问题内容: 我碰到了Java的一个奇怪角落(对我来说似乎很奇怪) o / p:是 o / p:错误 我观察到,如果我们将任意两个值(如我在示例中提到的float和double)与OR 进行比较,例如3.5、234.5、645.0,则输出为即两个值相等,否则输出为相等。 甚至我都试着做方法,但是没有运气。我错过了什么吗? 问题答案: 看一下每位计算机科学家应该了解的浮点数。 将无限多个实数压缩为有限

  • 问题内容: 我有一个String和一个int,可以说:和。什么是如果它们是相同的,看到的最快的方法还是(或者是有一个更快的方法?)? 这是Integer.parseInt和String.equals的源代码 问题答案: 会比 首先将num转换为O(n)的字符串,其中n是数字中的位数。然后它将再次进行字符串连接O(n),然后最终进行字符串比较。在这种情况下,字符串比较将是另一个O(n)-n是数字中的

  • 问题内容: 我正在浏览Java源代码中的接口,并遇到了以下这段代码: 从方法声明中,我知道这是一个通用方法,该方法返回一个Comparator类型,该Comparator类型可以从传递给它的映射条目中推断出来,也可以在该方法中明确提供。 真正让我失望的是返回值。似乎lambda表达式 被显式转换为。这是正确的吗? 我还注意到,表观演员包括。我之前从未见过将接口与类组合在一起的类型,但是在编译器中它

  • 我在查看接口的Java源代码时,遇到了以下代码片段: 从方法声明中,我得到这是一个通用方法,它返回一种类型的比较器,这种比较器要么是从传递给它的映射条目中推断出来的,要么是在方法中显式提供的。 真正让我恼火的是返回值。看来λ表达式 显式转换为 ?有人能澄清这一切吗?

  • 本文向大家介绍C#中Dynamic和Dictionary性能比较,包括了C#中Dynamic和Dictionary性能比较的使用技巧和注意事项,需要的朋友参考一下 开发中需要传递变参,考虑使用 dynamic 还是 Dictionary(准确地说是Dictionary<string,object>)。 dynamic 的编码体验显著优于 Dictionary,如果性能差距不大的话,我会选择使用dy