Java CAS基本實現(xiàn)原理代碼實例解析
一、前言
了解CAS,首先要清楚JUC,那么什么是JUC呢?JUC就是java.util.concurrent包的簡稱。它有核心就是CAS與AQS。CAS是java.util.concurrent.atomic包的基礎,如AtomicInteger、AtomicBoolean、AtomicLong等等類都是基于CAS。
什么是CAS呢?全稱Compare And Swap,比較并交換。CAS有三個操作數(shù),內(nèi)存值V,舊的預期值E,要修改的新值N。當且僅當預期值E和內(nèi)存值V相同時,將內(nèi)存值V修改為N,否則什么都不做。
二、實例
如果我們需要對一個數(shù)進行加法操作,應該怎樣去實現(xiàn)呢?我們模擬多個線程情況下進行操作。
ThreadDemo.java 實現(xiàn)一個Runnable接口
package com.spring.security.test;public class ThreadDemo implements Runnable {private int count = 0;@Overridepublic void run() {for (int i = 0; i < 100; i++) {addCount();}}private void addCount() {count++;}public int getCount() {return count;}}
ThreadTest.java 創(chuàng)建線程池,提交10個線程執(zhí)行,預期結果應該是1000
package com.spring.security.test;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadTest {public static void main(String[] args) {ExecutorService threadPool = Executors.newFixedThreadPool(10);ThreadDemo threadDemo = new ThreadDemo();for (int i = 0; i < 10; i++) {threadPool.submit(threadDemo);} threadPool.shutdown();System.out.println(threadDemo.getCount());}}
運行結果:874 或其他,與預期結果不符合。
執(zhí)行出來的結果并不是想象中的結果。這是為什么呢?這跟線程的執(zhí)行過程有關。
所以我們需要在改變count,將值從高速緩沖區(qū)刷新到主內(nèi)存后,讓其他線程重新讀取主內(nèi)存中的值到自己的工作內(nèi)存。
此時可以用volatile關鍵字。它的作用是保證對象在內(nèi)存中的可見性。
修改ThreadDemo中的count字段
private volatile int count = 0;
此時執(zhí)行結果:900 或其他,與預期結果不符合。
此時還是并未得出正確執(zhí)行結果。為什么?聽我細細道來。
線程安全主要體現(xiàn)在三個方面:
原子性:提供了互斥訪問,同一時刻只能有一個線程對它進行操作 可見性:一個線程對主內(nèi)存的修改可以及時的被其他線程觀察到 有序性:一個線程觀察其他線程中的指令執(zhí)行順序,由于指令重排序的存在,該觀察結果一般雜亂無序目前可見性已經(jīng)實現(xiàn)了,缺少原子性的操作,因為同一時刻,多個線程對其操作,會將改動后的最新值讀取到自己的工作內(nèi)存進行操作,最終只能得到后一個執(zhí)行線程操作的結果,所以相當于少了一步操作,就會造成數(shù)據(jù)的不一致。
此時可以使用JUC的Atomic包下面的類來進行操作。
Atomic類是使用CAS+volatile來實現(xiàn)原子性與可見性的。
我們來改造一下TheadDemo.java中的實現(xiàn)方法
package com.spring.security.test;import java.util.concurrent.atomic.AtomicInteger;public class ThreadDemo implements Runnable {private AtomicInteger count = new AtomicInteger(0);@Overridepublic void run() {for (int i = 0; i < 100; i++) {// 遞增count.getAndIncrement();}}public int getCount() {return count.get();}}
執(zhí)行結果: 1000,符合預期值。
接下來我們來分析一下AtomicInteger類的源碼:
private static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField('value')); } catch (Exception ex) { throw new Error(ex); }}private volatile int value;
Unsafe類是不安全的類,它提供了一些底層的方法,我們是不能使用這個類的。AtomicInteger的值保存在value中,而valueOffset是value在內(nèi)存中的偏移量,利用靜態(tài)代碼塊使其類一加載的時候就賦值。value值使用volatile,保證其可見性。
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
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表示當前對象,var2表示value在內(nèi)存中的偏移量,var4為增加的值。var5為調(diào)用底層方法獲取value的值
compareAndSwapInt方法通過var1和var2獲取當前內(nèi)存中的value值,并與var5進行比對,如果一致,就將var5+var4的值賦給value,并返回true,否則返回false
由do while語句可知,如果這次沒有設置進去值,就重復執(zhí)行此過程。這一過程稱為自旋。
compareAndSwapInt是JNI(Java Native Interface)提供的方法,可以是其他語言寫的。
三、與synchronized比較
使用synchronized進行加法:
package com.spring.security.test;public class ThreadDemo implements Runnable {private int count = 0;@Overridepublic void run() {for (int i = 0; i < 100; i++) {// 遞增synchronized (ThreadDemo.class) {count++;}}}public int getCount() {return count;}}
運行結果: 1000,符合預期值。
444
使用synchronized和AtomicInteger都能得到預期結果,但是他們之間各有什么劣勢呢?
synchronized是重量級鎖,是悲觀鎖,就是無論你線程之間發(fā)不發(fā)生競爭關系,它都認為會發(fā)生競爭,從而每次執(zhí)行都會加鎖。
在并發(fā)量大的情況下,如果鎖的時間較長,那將會嚴重影響系統(tǒng)性能。
CAS操作中我們可以看到getAndAddInt方法的自旋操作,如果長時間自旋,那么肯定會對系統(tǒng)造成壓力。而且如果value值從A->B->A,那么CAS就會認為這個值沒有被操作過,這個稱為CAS操作的'ABA'問題。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持好吧啦網(wǎng)。
相關文章:
