Java Concurrency(二)——J.U.C atomic包源碼解讀

java5以後的java.util.concurrent包是世界級併發大師Doug Lea的做品,裏面主要實現了html

  1. atomic包裏Integer/Long對應的原子類,主要基於CAS;
  2. 一些同步子,包括Lock,CountDownLatch,Semaphore,FutureTask等,這些都是基於AbstractQueuedSynchronizer類;
  3. 關於線程執行的Executors類等;
  4. 一些併發的集合類,好比ConcurrentHashMap,ConcurrentLinkedQueue,CopyOnWriteArrayList等。

今天咱們主要介紹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類作這個事情,這和事務原子性處理有點相似!

##循環時間長開銷大

  1. 即便沒有任何爭用也會作一些無用功
  2. 若是衝突比較嚴重的話,可能致使屢次修改失敗,for循環時間很長,可能比同步還慢

我在本身的電腦上用100個線程去修改一個共享變量,發現用AtomicInteger就比synchronized慢,可是都很快!因此仍是那個建議,不要過早優化,不要糾結究竟是1ms仍是2ms,除非測試以後發現確實是性能瓶頸,而後再仔細看一下,是否是代碼的使用有問題,要相信,能寫到JDK裏的代碼,通常都不會有問題。通常不到一天幾千萬上億的PV,應該是沒啥問題的。並且JVM對synchronized作了不少優化,包括鎖去除(Lock Elimination),輕量級鎖,偏向鎖等,因此寫代碼的時候首先仍是主要考慮代碼正確、清晰、可維護。

##只能保證一個共享變量的原子操做

若是併發約束條件涉及到兩個變量,就不能用兩個原子變量來達到總體的原子性,仍是得用同步。固然你也能夠用一個變通的方法,定義一個class,裏面包含約束條件涉及到的變量,而後用AtomicReference來實現原子性。

#總結

atomic包下的類好比AtomicInteger實現原子性的方法主要是依靠現代主流 CPU 都支持的CAS指令,它是經過Unsafe類的native方法調用的。通常而言性能比用鎖同步要好,可是都已經很好了,通常而言不會遇到性能問題,關鍵仍是看它的語義是否知足使用要求,以及是否可讓代碼更清新。

Refers

  1. http://my.oschina.net/lifany/blog/133513
  2. http://zl198751.iteye.com/blog/1848575
  3. http://blog.csdn.net/aesop_wubo/article/details/7537960
  4. http://my.oschina.net/u/177808/blog/166819
  5. http://www.blogjava.net/mstar/archive/2013/04/24/398351.html
  6. http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
  7. http://zeroturnaround.com/rebellabs/dangerous-code-how-to-be-unsafe-with-java-classes-objects-in-memory/
  8. http://www.pwendell.com/2012/08/13/java-lock-free-deepdive.html
相關文章
相關標籤/搜索