volatile屬性:可見性、保證有序性、不保證原子性。
Java的內存中所有的對象都存在主內存中,每個線程都有自己的棧和程序計數器,多個線程對同一個對象的變量讀取時,會將對象的變量從主內存中拷貝到自己的棧幀里(操作數棧),線程之間也無法直接訪問對方的操作數棧,只能通過主內存傳遞變量的值;
可見性:如果對聲明了volatile變量進行寫操作時,JVM會向處理器發送一條Lock前綴的指令,將這個變量所在緩存行的數據寫會到系統內存。 這一步確保了如果有其他線程對聲明了volatile變量進行修改,則立即更新主內存中數據。
有序性:但這時候其他處理器的緩存還是舊的,所以在多處理器環境下,為了保證各個處理器緩存一致,每個處理會通過嗅探在總線上傳播的數據來檢查 自己的緩存是否過期,當處理器發現自己緩存行對應的內存地址被修改了,就會將當前處理器的緩存行設置成無效狀態,當處理器要對這個數據進行修改操作時,會強制重新從系統內存把數據讀到處理器緩存里。 這一步確保了其他線程獲得的聲明了volatile變量都是從主內存中獲取最新的。
使用場景:狀態標記,后續會再寫另一個使用場景,單例模式下的volatile確保單例對象的返回的正確性。
原子操作:可以是一個步驟,也可以是多個步驟,但是其順序不可以被打斷,也不可以被切面只執行其中的一部分(不可中斷性)。
並發示例:
public class Counter { volatile int i=0; public void add(){ //非原子操作 可以加鎖synchronized或者ReentrantLock等保證原子性 i++; } } public class Main { public static void main(String[] args) throws Exception { final Counter counter=new Counter(); for(int i=0;i<5;i++){ new Thread(new Runnable(){ @Override public void run() { for(int j=0;j<10000;j++){ counter.add(); } System.out.println("Done......"); } }).start(); } //主線程進行等待 Thread.sleep(5000); System.out.println(counter.i); } }
其中上述add()方法對應的匯編指令,其中i++被編譯成下面四條指令,因此無法實現原子性:
public void add(); flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: aload_0 1: dup 2: getfield #2 // Field i:I 【從主內存中加載到操作數棧】 5: iconst_1 【將1賦值給變量】 6: iadd 7: putfield #2 // Field i:I 【將計算結果放回到主內存】 10: return LineNumberTable: line 11: 0 line 12: 10
CAS(Compare and swap)
Compare and swap 比較和交換,屬於硬件同步原語,處理器提供了基本內存操作的原子性保證。
CAS操作需要輸入兩個數值,一個舊值A和一個新值B,在操作期間對舊值進行比較,若沒有發生變化,才交換成新值,發生了變化則不交換。
Java中的sun.misc.Unsafe類,提供了compareAndSwapInt()和compareAndSwapLong()等幾個方法實現CAS。
JDK中很多工具類底層都是用到了CAS機制:
原子操作類:AtomicInteger、AtomicIntegerArray、AutomicIntegerFieldUpdater
並發操作類:ReentrantLock、ReentrantReadWriteLock、AQS、Semphore、CountDownLatch
Map:ConcurrentHashMap、ConcurrentSkipListMap
List:CopyOnWriteArrayList
Set:CopyOnWriteArraySet、ConcurrentSkipListSet
線程池:ThreadPoolExecutor
使用CAS實現鎖:
public class CounterUnsafe { volatile int i=0; private static Unsafe unsafe=null; private static long valueOffset; static{ try { //反射出unsafe對象 Field field=Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe=(Unsafe)field.get(null); //獲取Counter類中的字段i的偏移量 Field ifield=Counter.class.getDeclaredField("i"); valueOffset=unsafe.objectFieldOffset(ifield); }catch (NoSuchFieldException |IllegalAccessException e){ e.printStackTrace(); } } public void add(){ for(;;){ //拿到舊的值 int current=unsafe.getIntVolatile(this,valueOffset); //進行+1操作 int newValue=current+1; //如果寫會到堆內存成功 if(unsafe.compareAndSwapInt(this,valueOffset,current,newValue)){ break; } } } }
Counter類進行加鎖實現:
//對變量進行加鎖實現 public class Counter { volatile int i=0; Lock lock=new MyReentrantLock(); //自定義鎖 public void add(){ lock.lock(); i++; lock.unlock(); } } //自定義鎖 public class MyReentrantLock implements Lock{ //標記鎖的持有線程 AtomicReference<Thread> owner=new AtomicReference<>(); //等待的線程隊列 BlockQueue [ 如果當前沒有可用的空間 add 拋異常,offer 返回false,put阻塞][remove,poll,take] private LinkedBlockingQueue<Thread> waiters=new LinkedBlockingQueue<>(); @Override public void lock(){ //如果未獲取到鎖 if(!tryLock()){ waiters.offer(Thread.currentThread()); //1、線程掛起的幾種方式 [condition] [Suspend.resume() 容易造成死鎖,已經被棄用] [wait nofity 必須與synchronized 關鍵字一起使用] for(;;){ //判斷是否是線程的頭部,如果不是繼續循環 Thread peek = waiters.peek(); //peek拿到元素,元素本身不出隊列 if(peek!=Thread.currentThread()){ continue; } //如果是線程的頭部則嘗試着去拿鎖 if(tryLock()){ waiters.poll();// poll拿到元素,元素出隊列 return; } //沒有拿到鎖,則將線程掛起 LockSupport.park(); } } } @Override public boolean tryLock() { return owner.compareAndSet(null,Thread.currentThread()); } @Override public void unlock() { if(tryUnlock()){ Thread thread=waiters.peek(); if(thread!=null){ LockSupport.unpark(thread); //喚醒線程 } } } public boolean tryUnlock(){ if(owner.get()!=Thread.currentThread()){ throw new IllegalMonitorStateException(); }else{ return owner.compareAndSet(Thread.currentThread(),null); } } @Override public void lockInterruptibly() throws InterruptedException{ } @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } @Override public Condition newCondition() { return null; } }
其中Unsafe參見:Java魔法類:Unsafe應用解析