這次分享一些關于原子操作(CAS)的東西.
定義
CAS(Compare And Swap)是CPU的一個指令級別的操作,叫原子操作,原子操作是不可分割的,跟事務差不多,要么全部執行完成,要么不執行;
像這種操作有點類似阻塞鎖機制,但是使用阻塞鎖機制去控制的話,會比較消耗性能,而使用CAS操作的話會比使用鎖更快。
與synchronized性能比較,最簡單的一個測試Demo
public class AtomicNumber {AtomicInteger ai = new AtomicInteger(0);int i = 0;public synchronized void addInteger() {i ++ ;}public void addAtomicInteger() {ai.getAndIncrement();}public static void main(String[] args) throws InterruptedException {long time = System.currentTimeMillis();final AtomicNumber atomicNumber = new AtomicNumber();final CountDownLatch latch = new CountDownLatch(2);int count = 0;while(count<2) {new Thread(new Runnable() {public void run() {for(int j=0;j<200000;j++)atomicNumber.addAtomicInteger();latch.countDown();}}).start();count++;}latch.await();System.out.println("花費的時間:"+(System.currentTimeMillis()-time) + "ms");System.out.println("atomicInteger value:"+atomicNumber.ai.get());final CountDownLatch latch1 = new CountDownLatch(2);count = 0;time = System.currentTimeMillis();while(count<2) {new Thread(new Runnable() {public void run() {for(int j=0;j<200000;j++)atomicNumber.addInteger();latch1.countDown();}}).start();count++;}latch1.await();System.out.println("花費的時間:"+(System.currentTimeMillis()-time) + "ms");System.out.println("i value:"+atomicNumber.i);}
}//運行的結果:
花費的時間:14ms
atomicInteger value:400000
花費的時間:35ms
i value:400000
//很明顯,使用原子操作會比使用鎖機制要快。
CAS里面有三個操作數:1、內存地址(V);2、期望的值(A);3、新值(B)
主要思想:如果內存地址V的期望值等于A時,則將地址V賦值給新值B,如果不相等,算CAS操作失敗則不做任何操作;但觸發了CAS操作,如果內存地址上的值V不等于A的話,就會進入死循環,一直做CAS操作,一直到相等也就是成功,這個循環過程叫自旋。
存在的問題
1、ABA問題:即當取出內存地址V的時候,期望值從A變成新值B,然后有從B變成A值,然后再將V與期望值相比發現一值,其實這個過程確實發生了變化,只是結果值與初始值一致;
像這樣的問題,我們可以采用兩個方式,第一種就是在期望值變化的時候加上一個版本號(AtomicStampedReference)從A1變成B2變成A3這樣就能解決這樣的問題了,另外一種就是變化了就標記期望值已變化(AtomicMarkableReference)。
2、開銷比較大:長期處于自旋的CAS操作,會導致性能消耗。
3、只能保證只有一個共享變量進行原子操作,在Java中有個專門處理的類來解決這個問題(AtomicReference)
JDK提供的原子操作類
AtomicInteger
用法在上面的那個案例有了就不多說了,有幾個需要注意的地方:
1、在使用自增的方法有兩個:1)、getAndIncrement()?這個是先獲取值,在自增。2)、incrementAndGet()?先自增然后在獲取值。這兩個方法的差別有點類似?i++?以及 ++i的操作。
AtomicReference
這個是原子操作引用類型,結果來看,原子引用并不會改變原始的值。
static AtomicReference<UserInfo> reference = new AtomicReference<UserInfo>();public static void main(String[] args) {UserInfo userInfo1 = new UserInfo("Mark",12);reference.set(userInfo1);UserInfo userInfo2 = new UserInfo("Mark12",23);reference.compareAndSet(userInfo1, userInfo2);System.out.println("new data:");System.out.println(reference.get().getName());System.out.println(reference.get().getAge());System.out.println("old reference object:");System.out.println(userInfo1.getName());System.out.println(userInfo1.getAge());}static class UserInfo {String name;Integer age;public UserInfo(String name, Integer age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}}//運行結果:
new data:
Mark12
23
old reference object:
Mark
12
AtomicStampedReference
在期望值變化的時候加上一個版本號,在做CAS操作的時候,如果不一致會返回為false,并且不會做任何操作
/*** <p>需要一個初始值以及初始版本號*/static AtomicStampedReference<String> reference = new AtomicStampedReference<String>("Java",0);public static void main(String[] args) throws InterruptedException {final String value = reference.getReference();final int stamp = reference.getStamp();System.out.println("value:" + value + "====== version:"+stamp);Thread correctThread = new Thread(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName() + " old reference:"+value + " version:"+stamp + " CAS STATUS : " + reference.compareAndSet(value, value + " is the beast Language", stamp, 1));System.out.println(Thread.currentThread().getName() + "value:" + reference.getReference() + "====== version:"+reference.getStamp());}});Thread errorThread = new Thread(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName() + " old reference:"+reference.getReference() + " version:"+reference.getStamp() + " CAS STATUS : " + reference.compareAndSet(value, value + " Hello World!!", stamp, 2));System.out.println(Thread.currentThread().getName() + "value:" + reference.getReference() + "====== version:"+reference.getStamp());}});correctThread.start();correctThread.join();errorThread.start();errorThread.join();System.out.println("value:" + reference.getReference() + "====== version:"+reference.getStamp());}//運行結果:
value:Java====== version:0
value:Java====== version:0
Thread-0 old reference:Java version:0 CAS STATUS : true
Thread-1 old reference:Java version:0 CAS STATUS : false
Thread-0value:Java is the beast Language====== version:1
Thread-1value:Java is the beast Language====== version:1
目前就這么多啦,至于AtomicMarkableReference這個原子引用大家了自己去試一下跟AtomicStampedReference差不多,好啦該洗洗睡了。