Java學習筆記—多線程(原子類,java.util.concurrent.atomic包,轉載)
原子類
Java從JDK 1.5開始提供了java.util.concurrent.atomic包(以下簡稱Atomic包),這個包中
的原子操作類提供了一種用法簡單、性能高效、線程安全地更新一個變量的方式。
因為變量的類型有很多種,所以在Atomic包裏一共提供了13個類,屬於4種類型的原子更
新方式,分別是原子更新基本類型、原子更新數組、原子更新引用和原子更新屬性(字段)。
Atomic包裏的類基本都是使用Unsafe實現的包裝類
java.util.concurrent.atomic中的類可以分成4組:
- 標量類(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- 數組類:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 更新器類:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
- 復合變量類:AtomicMarkableReference,AtomicStampedReference
CAS
CAS(Compare-And-Swap)算法保證數據操作的原子性。
CAS 算法是硬件對於並發操作共享數據的支持。
CAS 包含了三個操作數:
內存值 V
預估值 A
更新值 B
當且僅當 V == A 時,V 將被賦值為 B,否則循環著不斷進行判斷 V 與 A 是否相等。
CAS的全稱為Compare-And-Swap,是一條CPU的原子指令,其作用是讓CPU比較後原子地更新某個位置的值,經過調查發現,其實現方式是基於硬件平臺的匯編指令,就是說CAS是靠硬件實現的,JVM只是封裝了匯編調用,那些AtomicInteger類便是使用了這些封裝後的接口。
atomic包的原子包下使用這種方案。例如AtomicInteger的getAndIncrement自增+1的方法,經查看源碼如下:
public final int getAndIncrement() { for (;;) {int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
源碼中for循環體的第一步先取得AtomicInteger裏存儲的數值,第二步對AtomicInteger的當前數值進行加1操作,關鍵的第三步調用compareAndSet方法來進行原子更新操作,該方法先檢查當前數值是否等於current,等於意味著AtomicInteger的值沒有被其他線程修改過,則將AtomicInteger的當前數值更新成next的值,如果不等compareAndSet方法會返回false,程序會進入for循環重新進行compareAndSet操作
Unsafe只提供了3種CAS方法:compareAndSwapObject、compare-AndSwapInt和compareAndSwapLong,具體實現都是使用了native方法。
/** * 如果當前數值是expected,則原子的將Java變量更新成x * @return 如果更新成功則返回true */ public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x); public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
native關鍵字說明其修飾的方法是一個原生態方法,方法對應的實現不是在當前文件,而是在用其他語言(如C和C++)實現的文件中。Java語言本身不能對操作系統底層進行訪問和操作,但是可以通過JNI接口調用其他語言來實現對底層的訪問。
JNI是Java本機接口(Java Native Interface),是一個本機編程接口,它是Java軟件開發工具箱(java Software Development Kit,SDK)的一部分。JNI允許Java代碼使用以其他語言編寫的代碼和代碼庫。Invocation API(JNI的一部分)可以用來將Java虛擬機(JVM)嵌入到本機應用程序中,從而允許程序員從本機代碼內部調用Java代碼。
標量類
- AtomicBoolean:原子更新布爾變量
- AtomicInteger:原子更新整型變量
- AtomicLong:原子更新長整型變量
- AtomicReference:原子更新引用類型
具體到每個類的源代碼中,提供的方法基本相同,這裏以AtomicInteger為例進行說明,AtomicInteger源碼主要的方法如下,原理主要都要都是采用了CAS,這裏不再累述。
public class AtomicInteger extends Number implements java.io.Serializable { public AtomicInteger(int initialValue) { value = initialValue; } //返回當前的值 public final int get() { return value; } //最終會設置成新值 public final void set(int newValue) { value = newValue; } public final void lazySet(int newValue) { unsafe.putOrderedInt(this, valueOffset, newValue); } //原子更新為新值並返回舊值 public final int getAndSet(int newValue) { for (;;) { int current = get(); if (compareAndSet(current, newValue)) return current; } } //如果輸入的值等於預期值,則以原子方式更新為新值 public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } //原子自增 public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } //原子方式將當前值與輸入值相加並返回結果 public final int getAndAdd(int delta) { for (;;) { int current = get(); int next = current + delta; if (compareAndSet(current, next)) return current; } } }
數組類
- AtomicIntegerArray:原子更新整型數組的某個元素
- AtomicLongArray:原子更新長整型數組的某個元素
- AtomicReferenceArray:原子更新引用類型數組的某個元素
AtomicIntegerArray類主要是提供原子的方式更新數組裏的整型,其常用方法如下。
- int addAndGet(int i,int delta):以原子方式將輸入值與數組中索引i的元素相加。
- boolean compareAndSet(int i,int expect,int update):如果當前值等於預期值,則以原子方式將數組位置i的元素設置成update值。
以上幾個類提供的方法幾乎一樣,所以這裏以AtomicIntegerArray為例進行講解
public class AtomicIntegerArray implements java.io.Serializable { private static final long serialVersionUID = 2862133569453604235L; private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final int base = unsafe.arrayBaseOffset(int[].class); private static final int shift; private final int[] array; private long checkedByteOffset(int i) { if (i < 0 || i >= array.length) throw new IndexOutOfBoundsException("index " + i); return byteOffset(i); } private static long byteOffset(int i) { return ((long) i << shift) + base; } public AtomicIntegerArray(int length) { array = new int[length]; } public final int get(int i) { return getRaw(checkedByteOffset(i)); } private int getRaw(long offset) { return unsafe.getIntVolatile(array, offset); } public final void set(int i, int newValue) { unsafe.putIntVolatile(array, checkedByteOffset(i), newValue); } public final void lazySet(int i, int newValue) { unsafe.putOrderedInt(array, checkedByteOffset(i), newValue); } public final int getAndSet(int i, int newValue) { long offset = checkedByteOffset(i); while (true) { int current = getRaw(offset); if (compareAndSetRaw(offset, current, newValue)) return current; } } public final boolean compareAndSet(int i, int expect, int update) { return compareAndSetRaw(checkedByteOffset(i), expect, update); } private boolean compareAndSetRaw(long offset, int expect, int update) { return unsafe.compareAndSwapInt(array, offset, expect, update); } }
這裏關鍵的實現也還是使用了CAS的策略,具體通過unsafe的native方法進行實現
原子更新字段類
如果需原子地更新某個類裏的某個字段時,就需要使用原子更新字段類,Atomic包提供
了以下3個類進行原子字段更新。
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
- AtomicLongFieldUpdater:原子更新長整型字段的更新器。
- AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起
來,可用於原子的更新數據和數據的版本號,可以解決使用CAS進行原子更新時可能出現的
ABA問題。
要想原子地更新字段類需要兩步。第一步,因為原子更新字段類都是抽象類,每次使用的
時候必須使用靜態方法newUpdater()創建一個更新器,並且需要設置想要更新的類和屬性。第
二步,更新類的字段(屬性)必須使用public volatile修飾符
public class AtomicIntegerFieldUpdaterTest { // 創建原子更新器,並設置需要更新的對象類和對象的屬性 private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater. newUpdater(User.class, "old"); public static void main(String[] args) { // 設置柯南的年齡是10歲 User conan = new User("conan", 10); // 柯南長了一歲,但是仍然會輸出舊的年齡 System.out.println(a.getAndIncrement(conan)); // 輸出柯南現在的年齡 System.out.println(a.get(conan)); } public static class User { private String name; public volatile int old; public User(String name, int old) { this.name = name; this.old = old; } public String getName() { return name; } public int getOld() { return old; } } }
代碼執行後輸出如下。
10 11
轉載自:
Java並發編程-原子類及並發工具類
Java學習筆記—多線程(原子類,java.util.concurrent.atomic包,轉載)