Java 比较和交换语义和性能规格数据: AMD 8220 4x 双核 @ 2.8 GHz规格数据: 英特尔 i7 四核 @ 2.67 GHz
Java中比较和交换的语义是什么?也就是说,just的比较和交换方法是否保证不同线程之间对原子整数实例的特定内存位置的有序访问,或者它是否保证对内存中所有位置的有序访问,即它的行为就好像它是易失性的(内存栅栏)。AtomicInteger
从文档中:
-
weakCompareAndSet
原子读取并有条件地写入变量,但不创建任何发生之前排序,因此不保证除目标以外的任何变量的先前或后续读取和写入。weakCompareAndSet
-
compareAndSet
以及所有其他读取和更新操作,例如具有读取和写入易失性变量的内存效应。getAndIncrement
从API文档中可以明显看出,它的行为就好像它是一个易失性变量一样。但是,应该只是更改其特定的内存位置。因此,如果该内存位置是单个处理器的缓存所独有的,则应该比常规的快得多。compareAndSet
weakCompareAndSet
weakCompareAndSet
compareAndSet
我之所以问这个问题,是因为我已经通过运行不同的线程(从1到8不等)对以下方法进行了基准测试,并且有(代码是用Scala编写的,Scala是一种静态编译的JVM语言,但它的含义和字节码转换都与Java的含义同构在这种情况下 - 这个简短的片段应该很清楚):threadnum
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
}
在 AMD 上,具有 4 个双 2.8 GHz 内核和一个 2.67 GHz 4 核 i7 处理器。JVM 是 Sun Server Hotspot JVM 1.6。结果显示没有性能差异。
规格数据: AMD 8220 4x 双核 @ 2.8 GHz
测试名称:loop_atomic_tlocal_cas
- 线程数: 1
运行时间: (显示最后 3 个) 7504.562 7502.817 7504.626 (平均值 = 7415.637 min = 7147.628 最大值 = 7504.886 )
- 线程数: 2
运行时间: (显示最后 3) 3751.553 3752.589 3751.519 (平均值 = 3713.5513 min = 3574.708 最大值 = 3752.949 )
- 线程数: 4
运行时间: (显示最后 3 个) 1890.055 1889.813 1890.047 (平均值 = 2065.7207 min = 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 min = 7057.03 最大值 = 7325.45 )
- 线程数: 2
运行时间: (显示最后 3) 3663.21 3665.838 3533.406 (平均值 = 3607.2149 min = 3529.177 max = 3665.838 )
- 线程数: 4
运行时间: (显示最后 3 个) 3664.163 1831.979 1835.07 (avg = 2014.2086 min = 1797.997 max = 3664.163 )
- 线程数: 8
运行时间:(显示最后 3 个)940.504 928.467 921.376 (平均值 = 943.665 min = 919.985 最大值 = 997.681 )
测试名称:loop_atomic_tlocal_weakcas
- 线程数: 1
运行时间: (显示最后 3) 7502.876 7502.857 7502.933 (平均值 = 7414.8132 min = 7145.869 最大值 = 7502.933 )
- 线程数: 2
运行时间: (显示最后 3) 3752.623 3751.53 3752.434 (平均值 = 3710.1782 min = 3574.398 最大值 = 3752.623 )
- 线程数: 4
运行次数: (显示最后 3 个) 1876.723 1881.069 1876.538 (平均值 = 4110.4221 min = 1804.62 max = 12467.351 )
- 线程数: 8
运行时间: (显示最后 3 个) 959.329 1010.53 969.767 (平均值 = 1072.8444 分钟 = 959.329 最大值 = 1880.049 )
规格数据: 英特尔 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 min = 8042.0742 max = 8132.8542 )
- 线程数: 2
运行时间: (显示最后 3 个) 4066.9559 4067.0414 4067.2080 (平均值 = 4086.0608 min = 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 min = 8043.3674 max = 8134.0418 )
- 线程数: 2
运行时间: (显示最后 3 个) 4066.5924 4066.5797 4066.6519 (avg = 4059.1911 min = 4025.0703 max = 4066.8547 )
- 线程数: 4
运行时间: (显示最后 3) 2033.2614 2035.5754 2036.9110 (平均值 = 2033.2958 分钟 = 2023.5082 最大值 = 2038.8750 )
虽然上面示例中的线程局部变量可能最终位于相同的缓存行中,但在我看来,常规 CAS 与其弱版本之间没有可观察到的性能差异。
这可能意味着,事实上,弱比较和交换充当完全成熟的内存栅栏,即充当好像它是一个易失性变量。
问:这个观察结果是否正确?另外,是否存在一个已知的架构或Java发行版,对于它,弱比较和设置实际上更快?如果不是,首先使用弱 CAS 的优势是什么?