簡介
當程序更新一個變量時,如果多線程同時更新這個變量,可能得到期望之外的值。
比如變量 i = 1
,A
線程更新 i+1
,B
線程也更新i+1
,經過兩個線程操作之後可能 i
不等於 3
,而是等於 2
。
因爲 A
和 B
線程在更新變量 i
的時候拿到的 i
都是 1
,這就是 線程不安全的更新操作,通常我們會使用 synchronized
來解決這個問題,synchronized
會保證多線程不會同時更新變量 i
。
而 Java 從 JDK 1.5
開始提供了 java.util.concurrent.atomic
包(以下簡稱Atomic包
),這個包中的 原子操作類 提供了一種用法簡單、性能高效、線程安全地更新一個變量的方式。
因爲變量的類型有很多種,所以在 Atomic
包裏一共提供了 12個
類,屬於以下 4
種類型的原子更新方式:
- 原子更新基本類型。
AtomicBoolean
:原子更新布爾類型。AtomicInteger
:原子更新整型。AtomicLong
:原子更新長整型。
- 原子更新數組。
AtomicIntegerArray
:原子更新整型數組裏的元素。AtomicLongArray
:原子更新長整型數組裏的元素。AtomicReferenceArray
:原子更新引用類型數組裏的元素。
- 原子更新引用。
AtomicReference
:原子更新對象引用。AtomicMarkableReference
:原子更新帶有標記位的對象引用。AtomicStampedReference
:原子更新帶有版本號的對象引用。
- 原子更新屬性(字段)。
AtomicIntegerFieldUpdater
:原子更新volatile
修飾的整型的字段的更新器。AtomicLongFieldUpdater
:原子更新volatile
修飾的長整型字段的更新器。AtomicReferenceFieldUpdater
:原子更新volatile
修飾的引用類型裏的字段的更新器。
Atomic
包裏的類基本都是使用 Unsafe
實現的包裝類。
原子更新基本類型
AtomicBoolean
:原子更新布爾類型。AtomicInteger
:原子更新整型。AtomicLong
:原子更新長整型。
以上3個類提供的方法幾乎一模一樣,所以本節僅以 AtomicInteger
爲例進行講解。
AtomicInteger
的常用方法如下:
int addAndGet(int delta)
:以原子方式將輸入的數值與實例中的值(AtomicInteger
裏的value
)相加,並返回結果。boolean compareAndSet(int expect,int update)
:如果輸入的數值等於預期值,則以原子方式將該值設置爲輸入的值。int getAndIncrement()
:以原子方式將當前值加1,注意,這裏返回的是自增前的值。void lazySet(int newValue)
:最終會設置成newValue
,使用lazySet
設置值後,可導致其他線程在之後的一小段時間內還是可以讀到舊的值。int getAndSet(int newValue)
:以原子方式設置爲newValue
的值,並返回舊值。
示例代碼:
public static void main(String[] args) {
AtomicInteger ai = new AtomicInteger(1);
System.out.println("ai.get() = " + ai.get());
System.out.println("ai.addAndGet(5) = " + ai.addAndGet(5));
System.out.println("ai.get() = " + ai.get());
System.out.println("ai.compareAndSet(ai.get(), 10) = " + ai.compareAndSet(ai.get(), 10));
System.out.println("ai.get() = " + ai.get());
System.out.println("ai.getAndIncrement() = " + ai.getAndIncrement());
System.out.println("ai.get() = " + ai.get());
ai.lazySet(8);
System.out.println("ai.lazySet(8)");
System.out.println("ai.get() = " + ai.get());
System.out.println("ai.getAndSet(5) = " + ai.getAndSet(5));
System.out.println("ai.get() = " + ai.get());
}
輸出:
ai.get() = 1
ai.addAndGet(5) = 6
ai.get() = 6
ai.compareAndSet(ai.get(), 10) = true
ai.get() = 10
ai.getAndIncrement() = 10
ai.get() = 11
ai.lazySet(8)
ai.get() = 8
ai.getAndSet(5) = 8
ai.get() = 5
AtomicInteger
的 getAndIncrement()
方法:
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
操作。
- 等於意味着
Atomic
包提供了 3
種基本類型的原子更新,但是 Java
的基本類型裏還有 char
、float
和 double
等。
那麼問題來了,如何原子的更新其他的基本類型呢?Atomic
包裏的類基本都是使用 Unsafe
實現的,讓我們一起看一下Unsafe的源碼:
/**
* 如果當前數值是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);
通過以上代碼,我們發現 Unsafe
只提供了 3 種 CAS 方法:compareAndSwapObject
、compareAndSwapInt
和 compareAndSwapLong
,再看 AtomicBoolean
源碼,發現它是先把 Boolean
轉換成 整型
,再使用 compareAndSwapInt
進行 CAS,所以原子更新 char
、float
和 double
變量也可以用類似的思路來實現。
原子更新數組
AtomicIntegerArray
:原子更新整型數組裏的元素。AtomicLongArray
:原子更新長整型數組裏的元素。AtomicReferenceArray
:原子更新引用類型數組裏的元素。
以上幾個類提供的方法幾乎一樣,所以僅以 AtomicIntegerArray
爲例進行介紹:AtomicIntegerArray
類主要是提供原子的方式更新數組裏的整型。
常用方法如下:
int addAndGet(int i,int delta)
:以原子方式將輸入值與數組中索引i的元素相加。boolean compareAndSet(int i,int expect,int update)
:如果當前值等於預期值,則以原子方式將數組位置i的元素設置成update值。
示例代碼:
public static void main(String[] args) {
int[] value = new int[]{1, 2};
AtomicIntegerArray ai = new AtomicIntegerArray(value);
System.out.println("ai.getAndSet(0, 3)");
ai.getAndSet(0, 3);
System.out.println("ai.get(0) = " + ai.get(0));
System.out.println("value[0] = " + value[0]);
ai.compareAndSet(1, 2, 5);
System.out.println("ai.compareAndSet(1, 2, 5)");
System.out.println("ai.get(1) = " + ai.get(1));
}
輸出結果:
ai.getAndSet(0, 3)
ai.get(0) = 3
value[0] = 1
ai.compareAndSet(1,2,5)
ai.get(1) = 5
需要注意的是,數組
value
通過構造方法傳遞進去,然後AtomicIntegerArray
會將當前數組複製一份,所以當AtomicIntegerArray
對內部的數組元素進行 修改 時,不會影響傳入的數組。
原子更新引用
AtomicReference
:原子更新對象引用。AtomicMarkableReference
:原子更新帶有標記位的對象引用。AtomicStampedReference
:原子更新帶有版本號的對象引用。該類將整數值與引用關聯起來,可用於原子的更新數據和數據的版本號,可以解決使用 CAS 進行原子更新時可能出現的 ABA問題。
以上幾個類提供的方法幾乎一樣,所以僅以AtomicReference
爲例進行介紹。
示例代碼:
public class AtomicReferenceTest {
public static AtomicReference<User> atomicUserRef = new
AtomicReference<User>();
public static void main(String[] args) {
User user = new User("103style", 20);
atomicUserRef.set(user);
System.out.println("atomicUserRef.get() = " + atomicUserRef.get().toString());
User updateUser = new User("xiaoke", 22);
atomicUserRef.compareAndSet(user, updateUser);
System.out.println("atomicUserRef.compareAndSet(user, updateUser);");
System.out.println("atomicUserRef.get() = " + atomicUserRef.get().toString());
}
static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "name='" + name + ", age=" + age;
}
}
}
輸出結果:
atomicUserRef.get() = name='103style, age=20
atomicUserRef.compareAndSet(user, updateUser);
atomicUserRef.get() = name='xiaoke, age=22
原子更新屬性(字段)
AtomicIntegerFieldUpdater
:原子更新volatile
修飾的整型的字段的更新器。AtomicLongFieldUpdater
:原子更新volatile
修飾的長整型字段的更新器。AtomicReferenceFieldUpdater
:原子更新volatile
修飾的引用類型裏的字段的更新器。
要想原子地更新字段類需要兩步:
- 因爲原子更新字段類都是抽象類,每次使用的時候必須使用靜態方法
newUpdater()
創建一個更新器,並且需要設置想要更新的類和屬性。 - 更新類的字段(屬性)必須使用
public volatile
修飾符。
以上3個類提供的方法幾乎一樣,所以僅以 AstomicIntegerFieldUpdater
爲例進行講解。
示例代碼:
public class AtomicIntegerFieldUpdaterTest {
public static void main(String[] args) {
// 創建原子更新器,並設置需要更新的對象類和對象的屬性
AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
newUpdater(User.class, "age");
// 設置柯南的年齡是10歲
User conan = new User("conan", 10);
// 柯南長了一歲,但是仍然會輸出舊的年齡
System.out.println(a.getAndIncrement(conan));
// 輸出柯南現在的年齡
System.out.println(a.get(conan));
}
public static class User {
public volatile int age;
private String name;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}
輸出結果:
10
11
鏈接:https://www.jianshu.com/p/cb64f79058c2