总览
原子操作如何在Java中工作,OpenJDK / Hotspot中是否存在可以转换为原子的当前替代方法。
反馈
在我以前的文章中, 对可变字段进行原子操作。 有人指出,无论好意如何,“修复”先前的行为都不太可能继续进行。
替代方法是添加@atomic注释。 这样做的好处是仅适用于新代码,而不会冒险破坏旧代码。
注意:故意使用小写字母,因为它*不*遵循当前的编码约定。
原子操作
任何带有@atomic列出的字段都将使整个表达式具有原子性。 非易失性和非原子性变量可以在开始时读取,或者在表达式完成后进行设置。 该表达式本身可能需要在某些平台,CAS操作或TSX上锁定,具体取决于CPU技术。
如果仅读取字段,或者也只写入一个字段,则该字段与volatile相同。
原子布尔
当前,AtomicBoolean使用4个字节,外加一个对象标头,并带有可能的填充(以及引用)。如果该字段是内联的,则可能看起来像这样
@atomic boolean flag;
// toggle the flag.
this.flag = !this.flag;
但是如何运作? 并非所有平台都支持1字节原子操作,例如Unsafe确实具有1字节CAS操作。 这可以通过掩膜来完成。
// possible replacement.
while(true) {int num = Unsafe.getUnsafe().getVolatileInt(this, FLAG_OFFSET & ~3); // word align the access.int value ^= 1 << ~(0xFF << (FLAG_OFFSET & 3) * 8) ;if (Unsafe.getUnsafe().compareAndSwapInt(this, FLAG_OFFSET & ~3, num, value))break;
}
原子双
不支持的类型是AtomicDouble,但这是AtomicLong的变体。 考虑这个例子。
@atomic double a = 1;
volatile double b = 2;a += b;
今天如何实施?
while(true) {double _b = Unsafe.getUnsafe().getVolatileDouble(this, B_OFFSET);double _a = Unsafe.getUnsafe().getVolatileDouble(this, A_OFFSET);long aAsLong = Double.doubleToRawLongBits(_a);double _sum = _a + _b;long sumAsLong = Double.doubleToRawLongBits(_a);if (Unsafe.getUnsafe().compareAndSwapLong(this, A_OFFSET, aAsLong, sumAsLong))break;
}
两个原子场
使用Intel TSX,您可以将硬件事务包装在多个字段中,但是如果没有TSX,该事务是否仍可以完成而无需求助于锁。
@atomic int a = 1, b = 2;a += b * (b % 2 == 0 ? 2 : 1);
如果字段在一起,仍然可以使用CAS来完成。 计划执行CAS2操作以检查两个64位值。 现在,此示例将使用两个4字节值。
assert A_OFFSET + 4 == B_OFFSET;
while(true) {long _ab = Unsafe.getUnsafe().getVolatileLong(this, A_OFFSET);int _a = getLowerInt(_ab);int _b = getHigherInt(_ab);int _sum = _a + _b * (_b % 2 == 0 ? 2 : 1);int _sum_ab = setLowerIntFor(_ab, _sum);if (Unsafe.getUnsafe().compareAndSwapLong(this, A_OFFSET, _ab, _sum_ab))break;
}
注意:此操作可以原子方式处理a或b或两者的更改。
原子参考
对不可变对象(例如BigDecimal)的常见用例操作。
@atomic BigDecimal a;
BigDecimal b;a = a.add(b);
可以在具有CompressedOops或32位JVM的系统上以这种方式实现。
BigDecimal _b = this.b;
while(true) {BigDecimal _a = (BigDecimal) Unsafe.getUnsafe().getVolatileObject(this, A_OFFSET);BigDecimal _sum = _a.add(_b);if (Unsafe.getUnsafe().compareAndSwapLong(this, A_OFFSET, _a, _sum))break;
}
更复杂的例子
对于您的平台,总会有一些例子过于复杂。 在带有TSX或HotSpot支持的系统的系统上,它们可能很好,但是您需要回退。
@atomic long a, b, c, d;a = (b = (c = d + 4) + 5 ) + 6;
当前不支持此功能,因为它在一个表达式中设置了多个long值。 但是,回退可能是使用现有的锁。
synchronized(this) {a = (b = (c = d + 4) + 5 ) + 6;
}
结论
通过添加注释,我们可以在常规字段中添加原子操作,而无需更改语法。 这将是对语言的自然扩展,而不会破坏向后的可比性。
翻译自: https://www.javacodegeeks.com/2014/07/adding-atomic-operations-to-java.html