您好,登錄后才能下訂單哦!
從本課開始學習并發編程的內容。主要介紹并發編程的基礎知識、鎖、內存模型、線程池、各種并發容器的使用。
原子
CAS
ABA
舉個例子:桌上的滿滿的一杯水,被打翻了,擦干凈收拾完后,再倒一杯,在別人看來以為還是之前那杯。線程1是當事人,線程2是別人,共享變量V是這一杯水,線程1和線程2同時拿到共享變量V的初始值A,各自處理;在線程1中把該值更新成B,又更新成A;線程2再來取值時發現還是A,結果又去做它的處理了。這種場景在某些情形下是不正確的,比如某商場推出活動,凡是會員卡剩余金額少于100元的,商場會給充值20元,持續2天,結果因為有的會員卡本來充了一次20,他消費了50,余額又少于100元了,這樣會再次充值20元,導致一張會員卡多次充值。
下面用代碼演示一個ABA問題
public void aba() {
AtomicInteger abaInt = new AtomicInteger(100);
Thread t1 = new Thread() {
public void run() {
abaInt.compareAndSet(100, 101);
System.out.println(String.format("thread t1, abaInt: %s", abaInt.get()));
abaInt.compareAndSet(101, 100);
System.out.println(String.format("thread t1, abaInt: %s", abaInt.get()));
}
};
Thread t2 = new Thread() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean success = abaInt.compareAndSet(100, 101);
System.out.println(String.format("thread t2, abaInt: %s, isSuccess: %s", abaInt.get(), success));
}
};
t1.start();
t2.start();
}
thread t1, abaInt: 101
thread t1, abaInt: 100
thread t2, abaInt: 101, isSuccess: true
在線程t2中比較的值100,其實已經不是之前的值100了,內存地址中的變量已經經歷過A -> B -> A的改變了。
ABA問題的根本原因是無法判斷變量是否真正被改變過,所以解決方案是給共享變量追加狀態,用來記錄共享變量是否被修改過。
原子類在java.util.concurrent.atomic
包下,有以下一些類:
AtomicBoolean 原子更新布爾值
AtomicInteger 原子增減數字的值
AtomicLong 原子更新長數字類型的值
AtomicReference 原子更新引用類型的值
AtomicIntegerArray 原子更新數字數組的元素
AtomicLongArray 原子更新長數字數組的元素
AtomicReferenceArray 原子更新引用類型數組的元素
AtomicIntegerFieldUpdater 原子更新類的數字類型字段的值
AtomicLongFieldUpdater 原子更新類的長數字類型字段的值
AtomicReferenceFieldUpdater 原子更新類的引用類型字段的值
AtomicStampedReference 原子更新帶版本號的引用類型的值,可以解決ABA問題
原子更新字段的步驟:
newUpdater()
創建更新器,并且指定要更新的類和字段AtomicIntegerFieldUpdater<User> upd = AtomicIntegerFieldUpdater.newUpdater(User.class, 'age');
public volatile
原子類實現原子操作的基礎是CAS,把操作交給硬件底層實現的原子性
下面給一個避免ABA問題的代碼:
public void noAba() {
AtomicStampedReference<Integer> noAbaInt = new AtomicStampedReference<Integer>(100, 0);
Thread t1 = new Thread() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
noAbaInt.compareAndSet(100, 101, noAbaInt.getStamp(), noAbaInt.getStamp() + 1);
System.out.println(String.format("thread t1, noAbaInt: %s", noAbaInt.getReference()));
noAbaInt.compareAndSet(101, 100, noAbaInt.getStamp(), noAbaInt.getStamp() + 1);
System.out.println(String.format("thread t1, noAbaInt: %s", noAbaInt.getReference()));
}
};
Thread t2 = new Thread() {
public void run() {
int stamp = noAbaInt.getStamp();
System.out.println("before stamp " + stamp);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after stamp " + noAbaInt.getStamp());
boolean success = noAbaInt.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(String.format("thread t2, noAbaInt: %s, isSuccess: %s", noAbaInt.getReference(), success));
}
};
t1.start();
t2.start();
}
before stamp 0
thread t1, noAbaInt: 101
thread t1, noAbaInt: 100
after stamp 2
thread t2, noAbaInt: 100, isSuccess: false
可以看到線程t2更新失敗了,由于版本號不是期望的值,所以更新失敗
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。