Web全堆疊~34.CAS
上一期
原子變數
Java并發包中的原子變數有以下幾種
AtomicBoolean:原子Boolean型別,常用來在程式中表示一個標志位,
AtomicInteger:原子Integer型別,
AtomicLong:原子Long型別,常用來在程式中生成唯一序列號,
AtomicReference:原子參考型別,用來以原子方式更新復雜型別,
除了這4個類,還有一些其他類,如針對陣列型別的類AtomicLongArray、AtomicReferenceArray,以及用于以原子方式更新物件中的欄位的類,如AtomicIntegerFieldUpdater、AtomicReferenceFieldUpdater等,
Java 8增加了幾個類,在高并發統計匯總的場景中更為適合,包括LongAdder、LongAccumulator、Double-Adder和DoubleAccumulator
AtomicInteger
AtomicInteger有兩個構造方法,第一個給定初始值,第二個初始值為0

可以直接獲取或設定AtomicInteger中的值,方法是

之所以稱為原子變數,是因為它包含一些以原子方式實作組合操作的方法
//以原子方式獲取舊值并設定新值
public final int getAndSet(int newValue)
//以原子方式獲取舊值并給當前值加1
public final int getAndIncrement()
//以原子方式獲取舊值并給當前值減1
public final int getAndDecrement()
//以原子方式獲取舊值并給當前值加delta
public final int getAndAdd(int delta)
//以原子方式給當前值加1并獲取新值
public final int incrementAndGet()
//以原子方式給當前值減1并獲取新值
public final int decrementAndGet()
//以原子方式給當前值加delta并獲取新值
public final int addAndGet(int delta)
這些方法的實作都依賴另一個public方法:
public final boolean compareAndSet(int expece,int update)
compareAndSet是一個非常重要的方法,比較并設定,簡稱為CAS,
該方法有兩個引數expect和update,以原子方式實作了如下功能:如果當前值等于expect,則更新為update,否則不更新,如果更新成功,回傳true,否則回傳false,AtomicInteger可以在程式中用作一個計數器,多個執行緒并發更新,也總能實作正確性,
public class Test extends Thread{
private static AtomicInteger counter = new AtomicInteger(0);
static class Visitor extends Thread {
@Override
public void run() {
for(int i = 0; i < 1000; i++) {
counter.incrementAndGet();
}
}
}
public static void main(String[]args) throws InterruptedException {
int num = 1000;
Thread[]threads = new Thread[num];
for(int i = 0; i < num; i++) {
threads[i]= new Visitor();
threads[i].start();
}
for(int i = 0; i < num; i++) {
threads[i].join();
}
System.out.println(counter.get());
}
}
基本原理和思維
AtomicInteger的使用方法是簡單直接的,它的主要內部成員是 :
//它的聲帶有volatile,這是必須的,以保證記憶體可見性
private volatile int value;
它的大部分更新方法實作都類似
public final int incrementAndGet() {
for(;;) {
int current = get();
int next = current + 1;
if(compareAndSet(current,next))
return next;
}
}
代碼主體是個死回圈,先獲取當前值current,計算期望的值next,然后呼叫CAS方法進行更新,如果更新沒有成功,說明value被別的執行緒改了,則再去取最新值并嘗試更新直到成功為止,
與synchronized鎖相比,這種原子更新方式代表一種不同的思維方式,synchronized是悲觀的,它假定更新很可能沖突,所以先獲取鎖,得到鎖后才更新,原子變數的更新邏輯是樂觀的,它假定沖突比較少,但使用CAS更新,也就是進行沖突檢測,如果確實沖突了,那也沒關系,繼續嘗試就好了,synchronized代表一種阻塞式演算法,得不到鎖的時候,進入鎖等待佇列,等待其他執行緒喚醒,有背景關系切換開銷,原子變數的更新邏輯是非阻塞式的,更新沖突的時候,它就重試,不會阻塞,不會有背景關系切換開銷,對于大部分比較簡單的操作,無論是在低并發還是高并發情況下,這種樂觀非阻塞方式的性能都遠高于悲觀阻塞式方式,
原子變數相對比較簡單,但對于復雜一些的資料結構和演算法,非阻塞方式往往難于實作和理解,幸運的是,Java并發包中已經提供了一些非阻塞容器,我們只需要會使用就可以了
鎖
除了可以實作樂觀非阻塞演算法之外,還可以實作悲觀阻塞式演算法,比如鎖,實際上,Java并發包中的所有阻塞式工具、容器、演算法也都是基于CAS的
public class Test extends Thread{
private AtomicInteger status = new AtomicInteger(0);
public void lock() {
while(!status.compareAndSet(0,1)) {
Thread.yield();
}
}
public void unlock() {
status.compareAndSet(1,0);
}
}
使用status表示鎖的狀態,0表示未鎖定,1表示鎖定,lock()、unlock()使用CAS方法更新,lock()只有在更新成功后才退出,實作了阻塞的效果,不過一般而言,這種阻塞方式過于消耗CPU
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/260403.html
標籤:java
上一篇:【Java 多執行緒 1】CountDownLatch
下一篇:python基礎1:數字、字串
