为Java添加@atomic操作
Java中的原子操作是怎么工作的?在目前的OpenJDK/Hotspot中,是否有其他方式转化为原子操作?
反馈
我在之前的文章 Making operations on volatile fields atomic中提到,如果没有仔细斟酌,直接“修正”以前的功能是不合适的。
转化原子操作,一种替代的方案是加入@atomic
注解。这样做的优点是可以在新代码中使用,而且不会破会旧代码的兼容性。
注:使用小写的名字是有意的,因为它不遵循当前的编码习惯。
原子操作
任何域加上@atomic
会使得整个表达式带有原子性。非volatile
或非atomic
的变量,可以在表达式(加上@atomic
的表达式)开始执行前读取或者在表达式完成后设置。表达式本身可能需要上锁,CAS(译者注:compare and swap)操作或者TSX,取决于依赖于平台的CPU。如果所有域是只读的,或者只有一个域可写,则与volatile
的功能一致。
原子布尔型
现在的AtomicBoolean
类加上对象头以及可能的填充字节(与引用一样)需要4字节。把这个域写入代码中,可能是这样的:
@atomic boolean flag; // toggle the flag. this.flag = !this.flag;
这段代码会如何运行呢?并不是所有的平台都支持一字节的原子操作,比如Unsafe类就没有一字节的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; }
(译者注:上面一段代码的while循环是一个CAS操作,确保取反操作的原子性。循环体第一句是获取修改前的内容。flag变量占用一个字节,这里直接获取包含flag变量的一个双字(4字节)。第二句是计算flag取反后,这个双字应该存放的内容,但这里的代码应该有问题,读者可以自己修改。第三句进行compareAndSwapInt操作,存入取反后的内容。整个取反过程保证了这四字节的内容不会被其他线程修改。)
原子双精度
Java标准库中不支持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或同时修改ab。
原子引用
一个作用在不可变对象上的普通用例,比如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; }
总结
通过添加注解,我们能为普通域增加原子操作而无需改变语法。这可以作为语言的自然扩展,而不会破坏后向兼容性。
原文链接: dzone 翻译: ImportNew.com - 文 学敏
译文链接: http://www.importnew.com/12438.html