分析:
如果是全局變量,i++是不安全的,因為java在操作i++的時候,是分步驟做的,可以理解為:
temp = i;
temp2 = i+1;
i=temp2;
如果線程1在執行第一條代碼的時候,線程2訪問i變量,這個時候,i的值還沒有變化,還是原來的值,所以是不安全的。
從更底層的角度講,主要是因為i++這個操作不是原子性的,這個會編譯成i= i +1; 所以會出現多線程訪問沖突問題。volatile雖然可以保證多線程對修改可見,但代碼中用到了
i++, 主要是i++不是原子性操作,這個會編譯成i = i +1,其實是做了3個步驟,一個是讀取,修改,寫入 。所以會出現多線程訪問沖突問題
解決方案:
在Java語言中,++i和i++操作並不是線程安全的,在使用的時候,不可避免的會用到synchronized關鍵字。而java.util.concurrent.AtomicInteger是一個提供原子操作的Integer類,其提供了線程安全且高效的原子操作,是線程安全的。
AtomicInteger類的底層實現原理是利用處理器的CAS操作(Compare And Swap,比較與交換,一種有名的無鎖算法)來檢測棧中的值是否被其他線程改變,如果被改變則CAS操作失敗。這種實現方法在CPU指令級別實現了原子操作,因此,其比使用synchronized來實現同步效率更高。
CAS操作過程都包含三個運算符:內存地址V、期望值E、新值N。當操作的時候,如果地址V上存放的值等於期望值E,則將地址V上的值賦為新值N,否則,不做任何操作,但是要返回原值是多少。這就保證比較和設置這兩個動作是原子操作。系統主要利用JNI(Java Native Interface,Java本地接口)來保證這個原子操作,它利用CPU硬件支持來完成,使用硬件提供swap和test_and_set指令,但CPU下同一指令的多個指令周期不可中斷,SMP(Symmetric Multi-Processing)中通過鎖總線支持這兩個指令的原子性
示例代碼:
package com.tree.blog.pool.cas; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class AtomicTest { volatile int initialValue=1; //Atomic包下的是基於CAS實現的,是線程安全的,不需要加鎖 AtomicInteger initialAtomicValue=new AtomicInteger(1); /** * i++ 是線程不安去的 * 這個語句在執行的時候實際分為了三部 * 1、在棧中給一個 tp=i * 2、執行i+1, tp2=i+1 * 3、賦值 給i完成,i=tp2 * 如果線程1在執行第一條代碼的時候,線程2訪問i變量,這個時候,i的值還沒有變化,還是原來的值,所以是不安全的 * @param args */ public static void main(String[] args) { AtomicTest test = new AtomicTest(); ExecutorService executorService = Executors.newFixedThreadPool(3000); //使用Callable+future可以接受返回值其它的不行 Runnable run = test::increment; Runnable runLock = test::incrementWithLock; Runnable atomicRun = test::atomicIncrement; //線程不安全,會出現多個線程得到相同的值 for (int i = 0; i <1000 ; i++) { executorService.submit(run); } //同步鎖,不會出粗,並發性能低 for (int i = 0; i <1000 ; i++) { executorService.submit(runLock); } //使用基於CAS實現的原子操作,不錯出現重復 for (int i = 0; i <1000 ; i++) { executorService.submit(atomicRun); } executorService.shutdown(); } /** * 同步鎖 執行自增 * @return */ public synchronized int incrementWithLock(){ System.out.println("線程名稱:"+Thread.currentThread().getName()+"-當前值:"+initialValue); return initialValue++; } /** * 執行自增 * @return */ public int increment(){ System.out.println("線程名稱:"+Thread.currentThread().getName()+"-當前值:"+initialValue); return initialValue++; } /** * 同步鎖 執行自增 * @return */ public synchronized int atomicIncrement(){ System.out.println("線程名稱:"+Thread.currentThread().getName()+"-當前值:"+initialAtomicValue.get()); return initialAtomicValue.getAndIncrement(); } }
參考: