Java併發編程:Java原子類

1、線程不安全

當多個線程訪問統一資源時,若是沒有作線程同步,可能形成線程不安全,致使數據出錯。舉例:java

@Slf4j public class ThreadUnsafe { // 用於計數的統計變量
    private static int count = 0; // 線程數量
    private static final int Thread_Count = 10; // 線程池
    private static ExecutorService executorService = Executors.newCachedThreadPool(); // 初始化值和線程數一致
    private static CountDownLatch downLatch = new CountDownLatch(Thread_Count); public static void main(String[] args) throws Exception{ for (int i = 0; i < Thread_Count; i++) { executorService.execute(() -> { for (int j = 0; j < 1000; j++) {  // 每一個線程執行1000次++操做 count++; } // 一個線程執行完
 downLatch.countDown(); }); } // 等待全部線程執行完
 downLatch.await(); log.info("count is {}", count); } }

當多個線程對count變量計數,每一個線程加1000次,10個線程理想狀態下是加10000次,但實際狀況並不是如此。算法

上面的代碼執行5次後,打印出來的count值分別爲 7130,8290,9370,8790,8132。從測試的結果看出,count的值並非咱們認爲的10000次,而都是小於10000次。數據庫

之因此出現上面的結果就是由於count++操做並不是原子的。它其實被分紅了三步:編程

tp1 = count;  //1
tp2 = tp1 + 1;  //2
count = tp2;  //3

因此 ,若是有兩個線程m和n要執行count++操做。若是是理想狀態下,m和n線程依次執行,m先執行完後,n再執行,即m1 -> m2 -> m3 -> n1 -> n2 -> n3,那麼結果是沒問題的。可是若是線程代碼的執行順序是m1 -> n1 -> m2 -> n2 -> m3 -> n3,那麼很明顯結果就會出錯。安全

而上面的測試結果也正是因爲沒有作線程同步,致使的線程在執行count++時,亂序執行後count的數值就不對了。多線程

2、原子操做

一、使用synchronized實現線程同步併發

對上面的代碼作一些改造,對count++操做加入synchronized關鍵字修飾,實現線程同步,以保證每一個線程在執行count++時,必須執行完成後,另外一個線程纔開始執行的。代碼以下:性能

@Slf4j public class ThreadUnsafe { // 用於計數的統計變量
    private static int count = 0; // 線程數量
    private static final int Thread_Count = 10; // 線程池
    private static ExecutorService executorService = Executors.newCachedThreadPool(); // 初始化值和線程數一致
    private static CountDownLatch downLatch = new CountDownLatch(Thread_Count); public static void main(String[] args) throws Exception{ for (int i = 0; i < Thread_Count; i++) { executorService.execute(() -> { for (int j = 0; j < 1000; j++) {  // 每一個線程執行1000次++操做
                    synchronized (ThreadUnsafe.class) { count++;  } } // 一個線程執行完
 downLatch.countDown(); }); } // 等待全部線程執行完
 downLatch.await(); log.info("count is {}", count); } }

將線程不安全的測試代碼添加synchronized關鍵字進行線程同步,保證線程在執行count++操做時,是依次執行完後,後面的線程纔開始執行的。synchronized關鍵字能夠實現原子性和可見性。測試

將上面的代碼執行5次後,打印出來的count值均爲10000,已是正確結果了。this

3、原子類

在 JDK1.5 中新增了 java.util.concurrent(J.U.C) 包,它創建在 CAS 之上。而CAS 採用了樂觀鎖思路,是非阻塞算法的一種常實現,相對於 synchronized 這種阻塞算法,它的性能更好。

一、樂觀鎖與CAS

在JDK5以前,Java是靠synchronized關鍵字保證線程同步的,這會致使有鎖,鎖機制存在如下問題:

在多線程競爭下,加鎖和釋放鎖會致使比較多的上下文切換和調度延時,引發性能問題;

一個線程持有鎖後,會致使其餘全部等待該鎖的線程掛起;

若是一個優先級高的線程等待一個優先級低的線程釋放鎖會致使線程優先級倒置,引發風險;

獨佔鎖採用的是悲觀鎖思路。synchronized就是一種獨佔鎖,它會致使其餘全部須要鎖的線程掛起。而另外一種更加有效的鎖就是樂觀鎖,CAS就是一種樂觀鎖。樂觀鎖,嚴格來講並非鎖。它是經過原子性來保證數據的同步,好比說數據庫的樂觀鎖,經過版本控制來實現,因此CAS不會保證線程同步,只是樂觀地認爲在數據更新期間沒有其餘線程參與。

CAS是一種無鎖算法。無鎖編程,即在不使用鎖的狀況下實現多線程間的同步,也就是在沒有線程被阻塞掛起的狀況下實現變量的同步。

CAS算法便是:Compare And Swap,比較並替換。

CAS算法存在着三個參數,內存值V,指望值A,以及須要更新的值B。當且僅當內存值V和指望值A相等的時候,纔會將內存值修改成B,不然什麼也不作,繼續循環檢查;

因爲CAS是CPU指令,咱們只能經過JNI與操做系統交互,關於CAS的方法都在sun.misc包下Unsafe的類裏,java.util.concurrent.atomic包下的原子類等經過CAS來實現原子操做。

CAS特色:

CAS是原子操做,保證併發安全,而不能保證併發同步

CAS是CPU的一個指令(須要JNI調用Native方法,才能調用CPU的指令)

CAS是非阻塞的、輕量級的樂觀鎖

二、AtomicInteger實現

JDK提供了原子操做類,指的是 java.util.concurrent.atomic 包下,一系列以Atomic開頭的包裝類。例如AtomicBoolean,AtomicInteger,AtomicLong。它們分別用於Boolean,Integer,Long類型的原子性操做。如下是AtomicInteger部分源代碼:

static { try { //獲取value屬性值在內存中的地址偏移量
            valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); }

AtomicInteger 的getAndIncrement 調用了Unsafe的getAndInt 方法完成+1原子操做。Unsafe類的getAndInt方法源碼以下:

//var1是this指針,var2是地址偏移量,var4是自增值,是自增1仍是自增N
    public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { //獲取內存值
            var5 = this.getIntVolatile(var1, var2); //var5是指望值,var5 + var4是要更新的值 //這個操做就是調用CAS的JNI,每一個線程將本身內存裏的內存值與var5指望值E做比較,若是相同,就將內存值更新爲var5 + var4,不然作自旋操做
            var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }

實現原子操做是基於compareAndSwapInt方法,更新前先取出內存值進行比較,和指望值一致後才更新。 

相關文章
相關標籤/搜索