1. 程式人生 > >java 原子操作的實現原理(2)

java 原子操作的實現原理(2)

Java中可以通過鎖和迴圈CAS的方式來實現原子操作。

這一節只講使用迴圈CAS實現原子操作:

(1)JVM中的CAS操作正是利用了處理器提供的CMPXCHG指令實現的。自旋CAS實現的基本思路就是迴圈進行CAS操作直到成功為止。下面是通過CAS執行緒安全的計數器方法safeCount和一個非執行緒安全的計數器count程式碼:

package com.heque;

import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger;

public class CASConcurrency {

    public static void main(String[] args) {         final Counter counter = new Counter();         List<Thread> list = new ArrayList<>(600);         long start = System.currentTimeMillis();         for (int i = 0; i < 100; i++) {             Thread t = new Thread(new Runnable() {

                @Override                 public void run() {                     for (int i = 0; i < 10000; i++) {                         counter.count();                         counter.safeCount();                     }                 }             });             list.add(t);         }         for (Thread t : list) {             t.start();         }         // 等待所有執行緒執行完成         for (Thread t : list) {             try {                 t.join();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }         System.out.println(counter.i);         System.out.println(counter.integer.get());         System.out.println("使用時間:" + (System.currentTimeMillis() - start+"ms"));     } }

class Counter{     public AtomicInteger integer = new AtomicInteger(0);     public Integer i = 0;     /** 使用CAS實現執行緒安全計數器 */     public void safeCount() {         for (;;) {             int i = integer.get();             boolean suc = integer.compareAndSet(i, ++i);             if (suc) {                 break;             }         }     }     /**      * 非執行緒安全計數器      */     public void count() {         i++;     }

}

(2)CAS實現原子操作的三大問題

1)ABA問題。

ABA問題。因為CAS需要在操作值的時候,檢查值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變數前面追加上版本號,每次變數更新的時候把版本號加1,那麼A→B→A就會變成1A→2B→3A。從Java 1.5開始,JDK的Atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法的作用是首先檢查當前引用是否等於預期引用,並且檢查當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。

2)迴圈時間長開銷大。

自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支援處理器提供的pause指令,那麼效率會有一定的提升。pause指令有兩個作用:第一,它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零;第二,它可以避免在退出迴圈的時候因記憶體順序衝突(Memory Order Violation)而引起CPU流水線被清空(CPU Pipeline Flush),從而提高CPU的執行效率。

3)只能保證一個共享變數的原子操作。

當對一個共享變數執行操作時,我們可以使用迴圈CAS的方式來保證原子操作,但是對多個共享變數操作時,迴圈CAS就無法保證操作的原子性,這個時候就可以用鎖。還有一個取巧的辦法,就是把多個共享變數合併成一個共享變數來操作。比如,有兩個共享變數i=2,j=a,合併一下ij=2a,然後用CAS來操作ij。從Java 1.5開始,JDK提供了AtomicReference類來保證引用物件之間的原子性,就可以把多個變數放在一個物件裡來進行CAS操作。