# CAS
CAS是compare and swap的縮寫,是一種多線程無鎖的編程方式。當修改多個線程可能同時操作的屬性時,給定`期望值`和`要更新的值`,如果`當前值`與`期望值`一致,則更新為要設置的值。CAS的底層是通過將讀取-比較-設置三個操作作為一個指令執行,在執行期間不會有其他線程改變變量。這保證了操作的原子性。
CAS其以樂觀的態度進行操作,不斷循環等待進行,對比而言,線程切換需要8萬個cup時鐘周期,而循環重試只需要幾個cup時鐘。
## 性能對比
下面以synchronized與cas進行對比,重點在于
```
// 1. 使用synchronized
static int count = 0;
final Object lock = new Object();
Thread ths [] = new Thread[10000];
for(int i=0;i<ths.length;i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
for(int i=0;i<10000;i++) {
synchronized (lock) {
count ++;
}
}
}
});
ths[i] = thread;
}
long begin = System.currentTimeMillis();
for(int i=0;i<ths.length;i++) {
ths[i].start();
ths[i].join();
}
System.out.println(count);// 100000000
System.out.println(System.currentTimeMillis() - begin); //5269
```
使用CAS的測試
```
final AtomicInteger count = new AtomicInteger(0);
Thread ths [] = new Thread[10000];
for(int i=0;i<ths.length;i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
for(int i=0;i<10000;i++) {
count.incrementAndGet();
}
}
});
ths[i] = thread;
}
long begin = System.currentTimeMillis();
for(int i=0;i<ths.length;i++) {
ths[i].start();
ths[i].join();
}
System.out.println(count.get());// 100000000
System.out.println(System.currentTimeMillis() - begin); // 1863
```
從上面可以看到,CAS比線程切換的方式快2.8倍以上。
## 主要類
* AtomicBoolean
* AtomicInteger
* AtomicIntegerArray
* AtomicIntegerFieldUpdater
* AtomicLong
* AtomicLongArray
* AtomicLongFieldUpdater
* AtomicMarkableReference 原子更新帶有標記位的引用類型
* AtomicReference 原子更新引用類型
* AtomicReferenceArray 原子更新引用數組
* AtomicReferenceFieldUpdater 原子更新引用類型里的字段
* AtomicStampedReference
**主要方法:**
* set(newVal)/get() 賦值、獲取當前值
* lazySet(newVal)
* getAndSet(newVal) 原子性設置值
* compareAndSet(exp,upd) 比較并設置值
* getAndIncrement()
* incrementAndGet()
## ABA問題
假如鏈表為: head->A->B->C,線程t1要將head->B,cas(A,B),此過程中線程t2進行了head->A->C->D,此時B已經處于游離狀態,B.next=null,切換到線程t1,t1發現header還是A,就進行交換
head->B,這時鏈表丟失了C和D。
以上就是由于ABA問題帶來的隱患,各種樂觀鎖的實現中通常都會用版本戳version來對記錄或對象標記,避免并發操作帶來的問題,在Java中,AtomicStampedReference<E>也實現了這個功能。
```
private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0); //初始值為100,初始時間戳為0
atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1); //cas 還要判斷時間戳
atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
```