主要參考java
https://www.jianshu.com/p/fb6e91b013cc算法
package volatileTest; public class CounterIPlusPlus { int i = 0; public static void main(String[] args) throws InterruptedException { System.out.println("Class Name:" + Thread.currentThread().getStackTrace()[1].getClassName()); System.out.println("JDK version:" + System.getProperty("java.version")); Long s = System.currentTimeMillis(); final CounterIPlusPlus c = new CounterIPlusPlus(); // TODO Auto-generated method stub for (int j = 0; j < 100; j++) { new Thread(new Runnable() { public void run() { for (int k = 0; k < 1000; k++) { c.add(); } } }).start(); } while (Thread.activeCount() > 1) // 保證前面的線程都執行完 Thread.yield(); System.out.println("spend time:" + (System.currentTimeMillis() - s)); System.out.println("result:" + c.i); } public void add() { ++i; } }
Class Name:volatileTest.CounterIPlusPlusatom
JDK version:1.7.0_79spa
spend time:22操作系統
result:99367線程
Class Name:volatileTest.CounterIPlusPlus3d
JDK version:1.7.0_79code
spend time:39blog
result:100000生命週期
Class Name:volatileTest.CounterIPlusPlus
JDK version:1.7.0_79
spend time:17
result:98962
package volatileTest; public class CounterSynchronized { public int i = 0; public static void main(String... strings) { System.out.println("Class Name:" + Thread.currentThread().getStackTrace()[1].getClassName()); System.out.println("JDK version:" + System.getProperty("java.version")); Long s = System.currentTimeMillis(); final CounterSynchronized t = new CounterSynchronized(); for (int i = 0; i < 100; i++) { new Thread() { public void run() { for (int j = 0; j < 1000; j++) { t.increase(); } } }.start(); } while (Thread.activeCount() > 1) // 保證前面的線程都執行完 Thread.yield(); System.out.println("spend time:" + (System.currentTimeMillis() - s)); System.out.println("result:" + t.i); } public synchronized void increase() { i++; } }
Class Name:volatileTest.CounterSynchronized
JDK version:1.7.0_79
spend time:26
result:100000
Class Name:volatileTest.CounterSynchronized
JDK version:1.7.0_79
spend time:44
result:100000
Class Name:volatileTest.CounterSynchronized
JDK version:1.7.0_79
spend time:39
result:100000
100個線程,能夠想象有100把鎖。每一個線程操做時,都會加鎖(讓其餘線程沒法操做)。實際上此時100個線程的執行已經不是並行執行了,已經變成了串行執行。
synchronized 每次加鎖解鎖都涉及上下文切換,須要涉及操做系統級別的操做,因此是重量級的操做。
package volatileTest; import java.util.concurrent.atomic.AtomicInteger; public class CounterCAS { private AtomicInteger i = new AtomicInteger(0); public static void main(String... strings) { System.out.println("Class Name:" + Thread.currentThread().getStackTrace()[1].getClassName()); System.out.println("JDK version:" + System.getProperty("java.version")); Long s = System.currentTimeMillis(); final CounterCAS c = new CounterCAS(); for (int j = 0; j < 100; j++) { new Thread(new Runnable() { public void run() { for (int k = 0; k < 1000; k++) { c.increment(); } } }).start(); } while (Thread.activeCount() > 1) // 保證前面的線程都執行完 Thread.yield(); System.out.println("spend time:" + (System.currentTimeMillis() - s)); System.out.println("result:" + c.i.get()); } public void increment() { for (;;) { int a = i.get(); if (i.compareAndSet(a, a + 1)) { break; } } } }
Class Name:volatileTest.CounterCAS
JDK version:1.7.0_79
spend time:19
result:100000
Class Name:volatileTest.CounterCAS
JDK version:1.7.0_79
spend time:20
result:100000
Class Name:volatileTest.CounterCAS
JDK version:1.7.0_79
spend time:20
result:100000
加鎖(使用synchronized)能夠實現同步操做。
使用CAS,使用AtomicInteger的 incrementAndGet 方法也能夠實現同步,原理呢?
CPU中指令集支持【Compare And Swap】這種原子性操做,好比Intel的cmpxchg指令。
100條線程同時【Compare And Swap】操做時,只有一個線程能真正成功的進行CAS操做,而後其餘線程都會獲取CAS操做失敗的信號(獲取操做失敗信號的線程就會什麼也不作,這些線程至關於空轉了一回)。
線程太多,直接計算機就卡死了,親測。
while (Thread.activeCount() > 1) // 保證前面的線程都執行完 Thread.yield();
synchronized操做i++,實質上並行操做已經變成了串行操做。至於線程A每次搶到鎖,加鎖後是作了1次i++操做就解鎖,仍是連續作了10次i++操做再解鎖,不知道,都有可能。這些也都不重要了,由於總共100 X 1000次i++操做至關因而一個一個順序執行的,因此最終數字不會出錯。
synchronized操做,實際上每一個線程都是足足執行了1000次自增,哪一個線程也沒少幹活,只不過各個線程都再爭搶先幹完,結束本身的1000次自增任務。詳細見後面《CounterSynchronizedDetail.java》的說明。
public void increment() { for (;;) { int a = i.get(); if (i.compareAndSet(a, a + 1)) { break; } } }
CAS操做i++,沒有線程的切換,全部線程都是一直在運行,只不過某個線程在作循環CAS操做時,若是一直【捕捉不到本身的備份數據跟內存數據一致的狀況】時,就只好一直空轉了,可是每一個線程的每次自增操做不走空,啥意思?就是必需要成功進行一次自增操做纔會跳出循環。而某次循環時幸運地成功【捕捉到本身的備份數據跟內存數據一致】,則在成功執行自增操做後進入下一次外層的for循環【for (int k = 0; k < 1000; k++)】。
循環CAS操做就比如不少人(實際上這裏是線程)在搶活兒幹,每次任務都是進行自增操做。可是每次的活兒(就是自增操做)只有一我的(實際上這裏是線程)能成功搶到,其餘人沒搶到只好繼續搶下一次機會。並且每一個人(實際上這裏是線程)不管搶活兒的能力如何,都要完成1000次的自增,因此早幹完(1000次循環)早休息,晚幹完晚休息。並且每次使用【for (;;)】搶活,搶不到的話線程就不會跳出【for (;;)】,必定要在裏面搶到一次活兒纔會跳出來進行下次外層的for循環【for (int k = 0; k < 1000; k++)】。
CAS算法中實際上每一個線程也都足足執行1000次自增,每一個線程也都是沒少幹活,各個線程也都是在爭取先完成本身的1000次自增,而後早點結束本身的1000次自增任務。
CAS中爲何不能有兩個線程同時搶到自增執行權呢?(以Intel爲例)由於CAS最底層依賴的是CPU的cmpxchg原子操做,若是A線程成功的完成了cmpxchg操做(線程A進行cmpxchg操做時硬件機制能保證其餘線程沒法進行cmpxchg操做),那麼內存數據就改變且立刻全部線程均可以看到最新的內存數據了。其餘線程再作cmpxchg操做時,發現本身手裏的備份數據已經和如今內存的數據不一致了,cmpxchg操做失敗,立馬進入下一次循環,爭取下一次能成功進行cmpxchg操做。
因此CAS最終仍然是依靠原子操做保證每次自增只有一個線程能成功執行,與synchronized不一樣的是CAS操做不涉及加鎖解鎖和線程間的切換。synchronized是在代碼層經過synchronized原語自己保證的;而CAS算法的原子操做時依賴於底層CPU硬件的指令(好比Intel中是cmpxchg)保證的。
抽象的說,就是:每次只有一個線程能真正成功的進行CAS操做,而後其餘線程都會獲取CAS操做失敗的信號,獲取操做失敗信號的線程就會什麼也不作,這些線程至關於空轉了一回,而後轉入本身的下一循環看是否可以成功地進行一次CAS操做。
package volatileTest; public class CounterSynchronizedDetail { public int i = 0; public static void main(String... strings) { System.out.println("Class Name:" + Thread.currentThread().getStackTrace()[1].getClassName()); System.out.println("JDK version:" + System.getProperty("java.version")); Long s = System.currentTimeMillis(); final CounterSynchronizedDetail t = new CounterSynchronizedDetail(); for (int i = 0; i < 100; i++) { new Thread() { public void run() { for (int j = 0; j < 1000; j++) { t.increase(); } } }.start(); } while (Thread.activeCount() > 1) // 保證前面的線程都執行完 Thread.yield(); System.out.println("spend time:" + (System.currentTimeMillis() - s)); System.out.println("result:" + t.i); } public synchronized void increase() { System.out.println(Thread.currentThread().getName()); i++; } }
將打印結果拷貝到notepad中。
從行號能夠看到多打印了四行,剩下的100000行都是線程名Thread-XX。說明全部線程一共執行了100000次。
進行計數,Thread-0以下,能夠看到是1000次匹配。
Thread-1呢?(注意要選擇【全詞匹配】)也是1000次匹配,每一個線程都是如此。
因此說雖然線程們爭先恐後的搶奪鎖的控制權,實際上那個線程都沒少幹活,搶來搶去都是爲了早點完成本身的任務而後結束本身全部的for循環操做。而後呢?若是是線程池,那麼線程完成全部任務就休息了,等待下次有任務來再次被調度。若是就是單個線程,那麼線程就結束生命週期了。