Java自增原子性問題(測試Volatile、AtomicInteger)

一、補充概念

1.什么是線程安全性?

《Java Concurrency in Practice》中有提到:當多個線程訪問某個類時,這個類始終都能表現出正確的行為,那么就稱這個類是線程安全的。

2.Java中的“同步”

Java中的主要同步機制是關鍵字“synchronized”,它提供了一種獨占的加鎖方式,但“同步”這個術語還包括volatile類型的變量,顯式鎖(Explicit Lock)以及原子變量。

3.原子性
 原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型)這個操作是不可分割的,那么我們說這個操作時原子操作。再比如:a++;這個操作實際是a = a + 1;是可分割的,所以他不是一個原子操作。非原子操作都會存在線程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那么我們稱它具有原子性。java的concurrent包下提供了一些原子類,我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。
Volatile修飾的成員變量在每次被線程訪問時,都強迫從共享內存重新讀取該成員的值,而且,當成員變量值發生變化時,強迫將變化的值重新寫入共享內存,這樣兩個不同的線程在訪問同一個共享變量的值時,始終看到的是同一個值。
代碼示例:

 
public class IncrementTestDemo {

    public static int count = 0;
    public static Counter counter = new Counter();
    public static AtomicInteger atomicInteger = new AtomicInteger(0);
    volatile public static int countVolatile = 0;
    
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        count++;
                        counter.increment();
                        atomicInteger.getAndIncrement();
                        countVolatile++;
                    }
                }
            }.start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("static count: " + count);
        System.out.println("Counter: " + counter.getValue());
        System.out.println("AtomicInteger: " + atomicInteger.intValue());
        System.out.println("countVolatile: " + countVolatile);
    }
    
}

class Counter {
    private int value;

    public synchronized int getValue() {
        return value;
    }

    public synchronized int increment() {
        return ++value;
    }

    public synchronized int decrement() {
        return --value;
    }
}

輸出結果

static count: 9952
Counter: 10000
AtomicInteger: 10000
countVolatile: 9979

第一行與最后一行,每次運行將得到不同的結果,但是中間兩行的結果相同。

通過上面的例子說明,要解決自增操作在多線程環境下線程不安全的問題,可以選擇使用Java提供的原子類,或者使用synchronized同步方法。

而通過Volatile關鍵字,并不能解決非原子操作的線程安全性。

java語言規范指出:為了獲取最佳的運行速度,允許線程保留共享變量的副本,當這個線程進入或者離開同步代碼塊時,才與共享成員變量進行比對,如果有變化再更新共享成員變量。這樣當多個線程同時訪問一個共享變量時,可能會存在值不同步的現象。

而volatile這個值的作用就是告訴VM:對于這個成員變量不能保存它的副本,要直接與共享成員變量交互。
建議:當多個線程同時訪問一個共享變量時,可以使用volatile,而當訪問的變量已在synchronized代碼塊中時,不必使用。
缺點:使用volatile將使得VM優化失去作用,導致效率較低,所以要在必要的時候使用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,360評論 11 349
  • layout: posttitle: 《Java并發編程的藝術》筆記categories: Javaexcerpt...
    xiaogmail閱讀 5,864評論 1 19
  • 讓孩子做到的,自己先做到。 孩子,沒關系——鄭淵潔 有一種奇跡叫做母愛——楊乃斌 他們是病人,不是罪人 用花證明自...
    春風拂面不如你閱讀 145評論 0 0
  • 光行者的道路會有許多挑戰和陷阱,然而最大的陷阱無非是虛幻小我所誘發的靈性驕傲,最大的挑戰則是為世人做個言行合一的典...
    昨夜的風閱讀 185評論 0 0
  • 樟樹留下婆娑的影 像不言不語的鬼魅 路燈投下朦朧的光 似黑夜傾訴的衷腸 默默 歸來的人走在寂靜的路上 遠處應有一盞...
    鮑鮑不愛說話閱讀 277評論 2 11