java5以後的java.util.concurrent包是世界級併發大師Doug Lea的做品,裏面主要實現了html
今天咱們主要介紹atomic包下相關內容。java
#CASc++
atomic包下的類主要基於現代主流 CPU 都支持的一種指令,Compare and Swap(CAS),這個指令能爲多線程編程帶來更好的性能。引用《Java Concurrency in Practice》裏的一段描述:算法
在這裏,CAS 指的是現代 CPU 普遍支持的一種對內存中的共享數據進行操做的一種特殊指令。這個指令會對內存中的共享數據作原子的讀寫操做。簡單介紹一下這個指令的操做過程:首先,CPU 會將內存中將要被更改的數據與指望的值作比較。而後,當這兩個值相等時,CPU 纔會將內存中的數值替換爲新的值。不然便不作操做。最後,CPU 會將舊的數值返回。這一系列的操做是原子的。它們雖然看似複雜,但倒是 Java 5 併發機制優於原有鎖機制的根本。簡單來講,CAS 的含義是「我認爲原有的值應該是什麼,若是是,則將原有的值更新爲新值,不然不作修改,並告訴我原來的值是多少」。spring
#AtomicInteger編程
private volatile int value;
AtomicInteger裏面只包含一個字段,用來記錄當前值,定義爲volatile是爲了知足可見性。tomcat
// setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
一開始定義了static變量Unsafe,AtomicInteger裏面的方法都是對unsafe裏面安全
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
方法的封裝。 咱們來看原子性的i++,多線程
public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } }
在一個無限循環裏面,首先獲取當前值,用當前值+1,而後調用併發
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
unsafe.compareAndSwapInt(this, valueOffset, expect, update)的含義是把this對象裏面valueOffset(在一開始static代碼裏面獲取)這個位置(即value值)跟expect比較,若是相等,則修改成update,返回true;若是不相等,說明在獲取到current以後有其餘線程修改過value的值,則從新來一遍,一直到修改爲功爲止。這裏就能夠看出,理論上來講,這個方法是有可能永遠不能返回的,實際而言,當併發衝突很嚴重,反覆compareAndSet(current, next)失敗,有可能也須要花費不少時間。
AtomicInteger裏面的其餘方法,基本相似;其餘類包括AtomicLong,AtomicReference等也是基本對Unsafe裏面compareAndSet的一個封裝。
#Unsafe
前面能夠看到Unsafe類在實現atomic的重要性。爲何有Unsafe這個class呢,基本緣由是Java不容許代碼直接操做內存,好處是更安全,通常不會出現內存泄露,由於有JVM的GC;壞處是有些底層調用執行不了。個人理解是,Unsafe就是這個java安全圍城通向好比c++這個不安全外圍的一道門,因此叫Unsafe嘛。Unsafe裏面基本都是native,即經過JNI調用c/c++等代碼。大部分是直接內存操做,以及後面會講到的掛起喚醒線程等,包括park和unpark。
前面到
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
這個方法就不是java代碼了,若是想看實現的話,須要下載OpenJDK源碼,裏面是c++代碼調用匯編代碼,blabla。我不建議你們再往下繼續了,緣由有幾個,一是咱們用java等高級語言的目的就是爲了不糾結複雜的底層細節,站在更高層的角度思考問題,並且java裏面還有更多的問題等待你去解決,更多的知識能夠學習呢!若是你說你已經把java徹底掌握了,包括把jdk源碼,tomcat、spring,xxxxx源碼都看過了,實在沒得看了,那我會說,多陪陪家人吧~除非你是JVM開發工程師,哦,那很差意思,大神,當我啥都沒說。。。。爲了完整性,我貼幾個參考連接http://www.blogjava.net/mstar/archive/2013/04/24/398351.html, http://zl198751.iteye.com/blog/1848575.
那麼若是獲取Unsafe呢?Unsafe有一個static方法能夠獲取Unsafe實例,以下
public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(2); if(var0.getClassLoader() != null) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
但是你若是在本身代碼裏使用,能夠編譯經過,可是運行時候報錯。由於裏面限制了調用getUnsafe()這個方法的類必須是啓動類加載器Bootstrap Loader。因此若是想在本身代碼裏面調用Unsafe的話(強烈建議不要這樣子作),能夠用Java的反射來實現:
static class UnsafeSupport { private static Unsafe unsafe; static { Field field; try { // 由反編譯Unsafe類得到的信息 field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); // 獲取靜態屬性,Unsafe在啓動JVM時隨rt.jar裝載 unsafe = (Unsafe) field.get(null); } catch (Exception e) { e.printStackTrace(); } } public static Unsafe getInstance() { // return Unsafe.getUnsafe();//沒有用,只能native獲取,不然會拋異常 return unsafe; } }
獲取到了Unsafe的實例以後,你照樣能夠本身實現Atomic類,再說一遍,強烈建議不要這樣作!!!
#CAS優勢
Compare and Set 是一個非阻塞的算法,這是它的優點。由於使用的是CPU支持的指令,提供了比原有的併發機制更好的性能和伸縮性。能夠認爲通常狀況下性能更好,而且也更容易使用(這纔是關鍵啊)。
#CAS缺點
##ABA問題
CAS操做容易致使ABA問題,也就是在作a++之間,a可能被多個線程修改過了,只不過回到了最初的值,這時CAS會認爲a的值沒有變。a在外面逛了一圈回來,你能保證它沒有作任何壞事,不能!!也許它討閒,把b的值減了一下,把c的值加了一下等等。解決ABA問題的方法有不少,能夠考慮增長一個修改計數(版本號),只有修改計數不變的且a值不變的狀況下才作a++,atomic包下有AtomicStampedReference類作這個事情,這和事務原子性處理有點相似!
##循環時間長開銷大
我在本身的電腦上用100個線程去修改一個共享變量,發現用AtomicInteger就比synchronized慢,可是都很快!因此仍是那個建議,不要過早優化,不要糾結究竟是1ms仍是2ms,除非測試以後發現確實是性能瓶頸,而後再仔細看一下,是否是代碼的使用有問題,要相信,能寫到JDK裏的代碼,通常都不會有問題。通常不到一天幾千萬上億的PV,應該是沒啥問題的。並且JVM對synchronized作了不少優化,包括鎖去除(Lock Elimination),輕量級鎖,偏向鎖等,因此寫代碼的時候首先仍是主要考慮代碼正確、清晰、可維護。
##只能保證一個共享變量的原子操做
若是併發約束條件涉及到兩個變量,就不能用兩個原子變量來達到總體的原子性,仍是得用同步。固然你也能夠用一個變通的方法,定義一個class,裏面包含約束條件涉及到的變量,而後用AtomicReference來實現原子性。
#總結
atomic包下的類好比AtomicInteger實現原子性的方法主要是依靠現代主流 CPU 都支持的CAS指令,它是經過Unsafe類的native方法調用的。通常而言性能比用鎖同步要好,可是都已經很好了,通常而言不會遇到性能問題,關鍵仍是看它的語義是否知足使用要求,以及是否可讓代碼更清新。
Refers