java:原子類的CAS
當一個處理器想要更新某個變數的值時,向匯流排發出LOCK#訊號,此時其他處理器的對該變數的操作請求將被阻塞,發出鎖定訊號的處理器將獨佔共享記憶體,於是更新就是原子性的了。
1、compareAndSet----比較並交換
AtomicInteger.conpareAndSet(int expect, indt update)
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
第一個引數為拿到的期望值,如果期望值沒有一致,進行update賦值,如果期望值不一致,證明資料被修改過,返回fasle,取消賦值
例子:
package com.jian8.juc.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 1.CAS是什麼?
* 1.1比較並交換
*/
public class CASDemo {
public static void main(String[] args) {
checkCAS();
}
public static void checkCAS(){
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data is " + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 2014) + "\t current data is " + atomicInteger.get());
}
}
輸出結果為:
true current data is 2019
false current data is 2019
2、CAS底層原理?對Unsafe的理解
比較當前工作記憶體中的值和主記憶體中的值,如果相同則執行規定操作,否則繼續比較知道主記憶體和工作記憶體中的值一直為止
-
atomicInteger.getAndIncrement();
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
-
Unsafe
-
是CAS核心類,由於Java方法無法直接訪問地層系統,需要通過本地(native)方法來訪問,Unsafe相當於一個後門,基於該類可以直接操作特定記憶體資料。Unsafe類存在於
sun.misc
Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接呼叫作業系統底層資源執行相應任務
-
變數valueOffset,表示該變數值在記憶體中的偏移地址,因為Unsafe就是根據記憶體便宜地址獲取資料的
-
變數value用volatile修飾,保證多執行緒之間的可見性
-
-
CAS是什麼
CAS全稱呼Compare-And-Swap,它是一條CPU併發原語
他的功能是判斷記憶體某個位置的值是否為預期值,如果是則更改為新的值,這個過程是原子的。
CAS併發原語體現在JAVA語言中就是sun.misc.Unsafe類中各個方法。呼叫Unsafe類中的CAS方法,JVM會幫我們實現CAS彙編指令。這是一種完全依賴於硬體的功能,通過他實現了原子操作。由於CAS是一種系統原語,原語屬於作業系統用語範疇,是由若干條指令組成的,用於完成某個功能的一個過程,並且原語的執行必須是連續的,在執行過程中不允許被中斷,也就是說CAS是一條CPU的原子指令,不會造成資料不一致問題。
//unsafe.getAndAddInt public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
var1 AtomicInteger物件本身
var2 該物件的引用地址
var4 需要變動的資料
var5 通過var1 var2找出的主記憶體中真實的值
用該物件前的值與var5比較;
如果相同,更新var5+var4並且返回true,
如果不同,繼續去之然後再比較,直到更新完成
3、CAS缺點
-
** 迴圈時間長,開銷大**
例如getAndAddInt方法執行,有個do while迴圈,如果CAS失敗,一直會進行嘗試,如果CAS長時間不成功,可能會給CPU帶來很大的開銷
-
只能保證一個共享變數的原子操作
對多個共享變數操作時,迴圈CAS就無法保證操作的原子性,這個時候就可以用鎖來保證原子性
-
ABA問題
三、原子類AtomicInteger的ABA問題?原子更新引用?
1、ABA如何產生
CAS演算法實現一個重要前提需要去除記憶體中某個時刻的資料並在當下時刻比較並替換,那麼在這個時間差類會導致資料的變化。
比如執行緒1從記憶體位置V取出A,執行緒2同時也從記憶體取出A,並且執行緒2進行一些操作將值改為B,然後執行緒2又將V位置資料改成A,這時候執行緒1進行CAS操作發現記憶體中的值依然時A,然後執行緒1操作成功。
==儘管執行緒1的CAS操作成功,但是不代表這個過程沒有問題==
2、如何解決?原子引用
示例程式碼:
package juc.cas;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicRefrenceDemo {
public static void main(String[] args) {
User z3 = new User("張三", 22);
User l4 = new User("李四", 23);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
}
}
@Getter
@ToString
@AllArgsConstructor
class User {
String userName;
int age;
}
輸出結果
true User(userName=李四, age=23)
false User(userName=李四, age=23)
3、時間戳的原子引用
新增機制,修改版本號
package com.jian8.juc.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* ABA問題解決
* AtomicStampedReference
*/
public class ABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
System.out.println("=====以下時ABA問題的產生=====");
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "Thread 1").start();
new Thread(() -> {
try {
//保證執行緒1完成一次ABA操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
}, "Thread 2").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=====以下時ABA問題的解決=====");
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本號" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第2次版本號" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第3次版本號" + atomicStampedReference.getStamp());
}, "Thread 3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本號" + stamp);
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t當前最新實際版本號:" + atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t當前最新實際值:" + atomicStampedReference.getReference());
}, "Thread 4")