【CAS之2】CAS操做實踐——i++的同步代碼與非同步代碼比較

參考網頁

主要參考java

https://www.jianshu.com/p/fb6e91b013cc算法

代碼1--i++非同步代碼

代碼

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;

}

}

運行結果1

Class Name:volatileTest.CounterIPlusPlusatom

JDK version:1.7.0_79spa

spend time:22操作系統

result:99367線程

運行結果2

Class Name:volatileTest.CounterIPlusPlus3d

JDK version:1.7.0_79code

spend time:39blog

result:100000生命週期

運行結果3

Class Name:volatileTest.CounterIPlusPlus

JDK version:1.7.0_79

spend time:17

result:98962

代碼1--i++同步時爲什麼失敗?--從字節碼指令角度解讀(最後成爲彙編指令也是多條指令)

查看字節碼指令

i++沒法獲得原子操做的保證

代碼2--synchronized完成i++操做

代碼

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++;

}

}

運行結果1

Class Name:volatileTest.CounterSynchronized

JDK version:1.7.0_79

spend time:26

result:100000

運行結果2

Class Name:volatileTest.CounterSynchronized

JDK version:1.7.0_79

spend time:44

result:100000

運行結果3

Class Name:volatileTest.CounterSynchronized

JDK version:1.7.0_79

spend time:39

result:100000

代碼2--synchronized完成i++操做分析

100個線程,能夠想象有100把鎖。每一個線程操做時,都會加鎖(讓其餘線程沒法操做)。實際上此時100個線程的執行已經不是並行執行了,已經變成了串行執行。

synchronized 每次加鎖解鎖都涉及上下文切換,須要涉及操做系統級別的操做,因此是重量級的操做。

代碼3--cas算法完成自增操做

代碼

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;

}

}

}

}

運行結果1

Class Name:volatileTest.CounterCAS

JDK version:1.7.0_79

spend time:19

result:100000

運行結果2

Class Name:volatileTest.CounterCAS

JDK version:1.7.0_79

spend time:20

result:100000

運行結果3

Class Name:volatileTest.CounterCAS

JDK version:1.7.0_79

spend time:20

result:100000

代碼3--cas自增操做同步時爲什麼成功?

加鎖(使用synchronized)能夠實現同步操做。

使用CAS,使用AtomicInteger的 incrementAndGet 方法也能夠實現同步,原理呢?

硬件技術上

CPU中指令集支持【Compare And Swap】這種原子性操做,好比Intel的cmpxchg指令。

原理上

100條線程同時【Compare And Swap】操做時,只有一個線程能真正成功的進行CAS操做,而後其餘線程都會獲取CAS操做失敗的信號(獲取操做失敗信號的線程就會什麼也不作,這些線程至關於空轉了一回)。

代碼中的一些細節

都是100個Thread裏面再重複1000次操做,爲什麼不直接開啓100 X 1000個線程?

線程太多,直接計算機就卡死了,親測。

保證前面的線程都執行完--是如何保證的?

while (Thread.activeCount() > 1)

// 保證前面的線程都執行完

Thread.yield();

★★★想象一下CAS實現自增操做是怎麼運行的?同時想象下synchronized如何實現的++i?比較一下這兩種場景

synchronized操做

synchronized操做i++,實質上並行操做已經變成了串行操做。至於線程A每次搶到鎖,加鎖後是作了1次i++操做就解鎖,仍是連續作了10次i++操做再解鎖,不知道,都有可能。這些也都不重要了,由於總共100 X 1000次i++操做至關因而一個一個順序執行的,因此最終數字不會出錯。

synchronized操做,實際上每一個線程都是足足執行了1000次自增,哪一個線程也沒少幹活,只不過各個線程都再爭搶先幹完,結束本身的1000次自增任務。詳細見後面《CounterSynchronizedDetail.java》的說明。

CAS操做

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操做。

CounterSynchronizedDetail.java

源碼

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循環操做。而後呢?若是是線程池,那麼線程完成全部任務就休息了,等待下次有任務來再次被調度。若是就是單個線程,那麼線程就結束生命週期了。

相關文章
相關標籤/搜索