为Java添加@atomic操作

jopen 10年前

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