当前访客身份:游客 [ 登录  | 注册加入尚学堂]
启用新域名sxt.cn
新闻资讯

为Java添加@atomic操作

helloworld 发表于 2年前  | 评论(0 )| 阅读次数(690 )|   0 人收藏此文章,   我要收藏

Java中的原子操作是怎么工作的?在目前的OpenJDK/Hotspot中,是否有其他方式转化为原子操作?

反馈

我在之前的文章 Making operations on volatile fields atomic中提到,如果没有仔细斟酌,直接“修正”以前的功能是不合适的。

转化原子操作,一种替代的方案是加入@atomic注解。这样做的优点是可以在新代码中使用,而且不会破会旧代码的兼容性。

注:使用小写的名字是有意的,因为它遵循当前的编码习惯。

原子操作

任何域加上@atomic会使得整个表达式带有原子性。非volatile或非atomic的变量,可以在表达式(加上@atomic的表达式)开始执行前读取或者在表达式完成后设置。表达式本身可能需要上锁,CAS(译者注:compare and swap)操作或者TSX,取决于依赖于平台的CPU。如果所有域是只读的,或者只有一个域可写,则与volatile的功能一致。

原子布尔型

现在的AtomicBoolean类加上对象头以及可能的填充字节(与引用一样)需要4字节。把这个域写入代码中,可能是这样的:


 	
@atomicbooleanflag;
// toggle the flag.
this.flag = !this.flag;


这段代码会如何运行呢?并不是所有的平台都支持一字节的原子操作,比如Unsafe类就没有一字节的CAS操作。这可以通过布尔屏蔽来实现。


 
	
// possible replacement.
while(true) {
    intnum = Unsafe.getUnsafe().getVolatileInt(this, FLAG_OFFSET & ~3);// word align the access.
    intvalue ^=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的变种。请看下面的例子:


@atomicdoublea =1;
volatiledoubleb =2;
a += b;


放在今天,可能会怎么实现呢?


 	
while(true) {
    double_b = Unsafe.getUnsafe().getVolatileDouble(this, B_OFFSET);
    double_a = Unsafe.getUnsafe().getVolatileDouble(this, A_OFFSET);
    longaAsLong = Double.doubleToRawLongBits(_a);
    double_sum = _a + _b;
    longsumAsLong = Double.doubleToRawLongBits(_a);
    if(Unsafe.getUnsafe().compareAndSwapLong(this, A_OFFSET, aAsLong, sumAsLong))
        break;
}


两个原子域

使用Intel的TSX,你可以把几个域打包到一个硬件事务中。但如果你的平台不支持TSX,又不使用锁,这可行么?


 
@atomicinta =1, b =2;
a += b * (b %2==0?2:1);


如果有多个域一起是原子的,使用CAS仍然可行。将来会设计一个CAS2操作能够检查两个64位的值。所以目前,下面的例子使用两个4字节的变量。


assertA_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:


 	
@atomicBigDecimal 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的系统上。但在你的系统上,可能需要一些回退(使用旧的技术达到目的)。


 
@atomiclonga, b, c, d;
a = (b = (c = d +4) +5) +6;


目前,上面的例子还不支持,它在一个表达式中设置了多个long值。但是,退一步说可以使用已有的锁机制。


 	
synchronized(this) {
    a = (b = (c = d +4) +5) +6;
}


总结

通过添加注解,我们能为普通域增加原子操作而无需改变语法。这可以作为语言的自然扩展,而不会破坏后向兼容性。

分享到:0
关注微信,跟着我们扩展技术视野。每天推送IT新技术文章,每周聚焦一门新技术。微信二维码如下:
微信公众账号:尚学堂(微信号:bjsxt-java)
声明:博客文章版权属于原创作者,受法律保护。如果侵犯了您的权利,请联系管理员,我们将及时删除!
(邮箱:webmaster#sxt.cn(#换为@))
北京总部地址:北京市海淀区西三旗桥东建材城西路85号神州科技园B座三层尚学堂 咨询电话:400-009-1906 010-56233821
Copyright 2007-2015 北京尚学堂科技有限公司 京ICP备13018289号-1 京公网安备11010802015183