二進位制Etcd叢集升級
簡介
CAS(Compare and swap)比較和替換是設計併發演算法時用到的一種技術。簡單來說,比較和替換是使用一個期望值和一個變數的當前值進行比較,如果當前變數的值與我們期望的值相等,就使用一個新值替換當前變數的值。
AtomicInteger atomicInteger = new AtomicInteger(10); System.out.println(atomicInteger.compareAndSet(10, 4)); System.out.println(atomicInteger.compareAndSet(10, 2)); System.out.println(atomicInteger);
底層原理
以AtomicInteger的getAndIncrement方法為例:
public final int getAndIncrement() {
//this:當前物件
//valueOffset:記憶體地址偏移量(記憶體地址)
return unsafe.getAndAddInt(this, valueOffset, 1);
}
這裡由unsafe呼叫getAndAddInt方法。
Unsafe
Unsafe是CAS的核心類,由於Java方法無法直接訪問底層系統,需要通過本地(native)方法類訪問,Unsafe相當於一個後門,基於該類可以直接操作特定記憶體的資料。Unsafe存在於sun.misc包中,其內部方法操作可以像C的指標一樣直接操作記憶體,因為java中CAS操作的執行依賴於Unsafe類的方法。
Unsafe類中的方法都可以直接呼叫作業系統底層資源執行相應任務。
變數valueOffset,表示該變數值在記憶體中的偏移地址,因為Unsafe就是根據記憶體便宜地址獲取資料的
變數用value修飾,保證了多執行緒之間的記憶體可見性。
CAS(Compare and swap),是一條CPU併發原語。它的功能是判斷記憶體某個位置的值是否為預期值,如果是則更改為新的值,這個過程是原子的。
原語的執行必須是連續的,執行過程中不允許打斷,所以CAS是一條CPU的原子指令,所以不會造成所謂的資料不一致問題。
var1:AtomicInteger物件本身
var2:該物件值的引用地址
var4:需要變動的值
var5:是用過var1 var2找出的主記憶體中真實的值
compareAndSwapInt:var2和var5比較,相同則 var5+var4,並返回true,跳出do-while迴圈。如果不同,繼續取值再比較,直至更新完成。
CAS缺點
1)如果CAS失敗,會一直嘗試,如果CAS長時間不成功,可能會給CPU帶來很大的開銷
2)只能保證一個共享變數的原子操作
3)ABA問題
ABA
如果一個執行緒1從記憶體位置V中取出A,這時候另一個執行緒2也從記憶體中取出A,並且執行緒2進行了一些操作將值程式設計了B,然後執行緒2又將V位置的資料變成A,這時候執行緒one進行CAS操作發現記憶體中仍然是A,然後執行緒1操作成功。
儘管執行緒1的CAS操作成功,但是不代表這個過程就是沒有問題的。
原子引用
AtomicReference:原子引用
//toString,Getter,Setter省略
class Stu{
private String name;
public Stu(String name) {
this.name = name;
}
}
public class AtomicReferenceDemo {
public static void main(String[] args) {
Stu zs = new Stu("zs");
Stu ls = new Stu("ls");
AtomicReference<Stu> reference = new AtomicReference<>(zs);
System.out.println(reference.compareAndSet(zs,ls));
System.out.println(reference.compareAndSet(zs,ls));
}
}
ABA問題解決
新增一種機制,修改版本號(類似時間戳)
存在ABA問題的程式碼:
new Thread(()->{
atomicReference.compareAndSet(10,11);
atomicReference.compareAndSet(11,10);
},"t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(10,11));
},"t2").start();
使用AtomicStampedReference解決:
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(10,1);
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("t3 first stamp :"+stamp);
atomicStampedReference.compareAndSet(10,11,1,2);
atomicStampedReference.compareAndSet(11,10,2,3);
},"t3").start();
new Thread(()->{
try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(atomicStampedReference.compareAndSet(10, 11, 1, 2));
},"t4").start();
}