為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