文章出處

先上一段代碼:

package test;

public class Program {

    public static int i = 0;

    private static class Next extends Thread {

        public void run() {
            i = i + 1;
            System.out.println(i);
        }
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new Next());
            threads[i].start();
        }
    }
}

代碼很簡單,10個線程,1個共享變量,每個線程在run的時候,將變量+1,反復運行多次,可能會輸出類似下面的結果:

1
4
3
6
2
5
7
8
9
9

最后輸出了2個9,顯然有2個線程打架了,原因:

i = i + 1,雖然只有一行代碼,但在計算機內部執行時,至少會拆成3條指令

a) 讀取 i 的值,將其復制到本地的(副本)變量中

b) 將本地變量值+1

c) 將本地變量的值,覆蓋到 i 上

假如有2個線程先后到達步驟a),但尚未完成步驟b),這時就出問題了,會生成相同的值。要解決這個問題,當然可以通過加鎖(或synchronized),類似下面這樣,代價是犧牲性能。

    private static class Next extends Thread {

        public void run() {
            synchronized (this) {
                i = i + 1;
            }
            System.out.println(i);
        }
    }

jdk的并發包里提供了很多原子變量,可以在"不加鎖"(注:OS底層其實還是有鎖的,只不過相對java里的synchronized性能要好很多)的情況下解決這個問題,參考下面的用法:

package test;

import java.util.concurrent.atomic.AtomicInteger;

public class Program {

    public static AtomicInteger i = new AtomicInteger(0);

    private static class Next extends Thread {

        public void run() {
            int x = i.incrementAndGet();
            System.out.println(x);
        }
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new Next());
            threads[i].start();
        }
    }
}

  

實現原理,可以從源碼略知一二:

    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

1、最外層是一個死循環

2、先獲取舊值,將其復制到一個局部變量上

3、將局部變量值+1

4、比較舊值是否變化,如果沒變化,說明沒有其它線程對舊值修改,直接將新值覆蓋到舊值,并返回新值,退出循環

5、如果舊值被修改了,開始下一輪循環,重復剛才這一系列操作,直到退出循環。

 

所以,第4步的compareAndSet其實是關鍵,繼續看源碼:

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

最終看到的是一個native方法(說明依賴不同OS的原生實現)

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

再往下跟,就得有點c++/c/匯編功底了,有興趣的可自己研究下參考文章中的第2個鏈接文章

 

參考文章:

http://ifeve.com/concurrent-collections-8/

http://www.blogjava.net/mstar/archive/2013/04/24/398351.html

http://ifeve.com/13840/


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()