Java拾遺 - CAS算法以及immutable變量的線程安全

綜述CAS

1. 鎖的機制

爲了實現線程安全,對於一些關鍵變量必須加鎖

常用的鎖機制有兩種:

1、悲觀鎖:假定會發生併發衝突,屏蔽一切可能違反數據完整性的操作。悲觀鎖的實現,往往依靠底層提供的鎖機制;悲觀鎖會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。

2、樂觀鎖:假設不會發生併發衝突,每次不加鎖而是假設沒有衝突而去完成某項操作,只在提交操作時檢查是否違反數據完整性。如果因爲衝突失敗就重試,直到成功爲止。樂觀鎖大多是基於數據版本記錄機制實現。爲數據增加一個版本標識,比如在基於數據庫表的版本解決方案中,一般是通過爲數據庫表增加一個 “version” 字段來實現。讀取出數據時,將此版本號一同讀出,之後更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大於數據庫表當前版本號,則予以更新,否則認爲是過期數據。

鎖機制存在以下問題:

(1)在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題。
(2)一個線程持有鎖會導致其它所有需要此鎖的線程掛起。
(3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先級倒置,引起性能風險。

在JAVA1.5之前,JAVA語言是靠Synchronzied關鍵字保證鎖同步,這是一種悲觀鎖,效率低且易於死鎖,有沒有一種鎖,能夠在資源被鎖時也不影響其他進程?

CAS

JAVA1.6之後,java.util.concurrent(J.U.C)種提供的atomic包中的類,使用的是樂觀鎖,用到的機制就是CAS,CAS(Compare and Swap)有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改爲B,否則什麼都不做

以AtomicInteger包爲例

  /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

可見實現CAS算法的部分是來自於UNSAFE這個包

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

再來到UNSAFE包中,發現這個方法是一個native方法,網上其他教程甚至翻到了系統彙編語言,這裏我認爲理解這個CAS的實現原理就好,而底層上他就是利用了CPU的一個快捷計算方式。

那麼我們開始使用CAS實現一個樂觀鎖鎖

首先是一組不帶鎖的1234輸出
代碼實現如下:

 //unsafe
    private static int flagnum__ = 0;

    static class task__ implements Runnable{

        @Override
        public void run() {
            while (flagnum__ < 100) {
                flagnum__++;
                System.out.println(flagnum__);
            }
        }
    }
      public static void main(String args[]) {
        Executor executor = Executors.newFixedThreadPool(2);
        executor.execute(new task__());
        executor.execute(new task__());
    }

輸出結果如下:

1
3
4
5
6
7
8
9
...
````
可見由於線程不安全的問題,很多的數字都跳過去了。
那我們利用CAS算法實現一個”穩定“的樂觀鎖





<div class="se-preview-section-delimiter"></div>
//Thread safe now?
private static boolean compareAndSet(int old_, int new_) {
    if (old_ != flagnum__) {
        return false;
    } else {
        flagnum__ = new_;
        return true;
    }
}
static class task2__ implements Runnable{
    @Override
    public void run() {
        while (flagnum__ < 100) {
            int old_val = flagnum__;
            if (compareAndSet(old_val, old_val + 1))
            System.out.println(flagnum__);
        }
    }
}
    public static void main(String args[]) {
    Executor executor = Executors.newFixedThreadPool(2);

// executor.execute(new task__());
// executor.execute(new task__());
executor.execute(new task2__());
executor.execute(new task2__());
}

結果如下




<div class="se-preview-section-delimiter"></div>

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2

k可見依然是一個線程不安全的,依然無法實現樂觀鎖。看來Integer是無法實現樂觀鎖的(?),接下來我們運用Unsafe裏面的類去實現一個樂觀鎖,即調用JVM底層的實現,並與Synchronized關鍵字實現的悲觀鎖的效率做一個對比





<div class="se-preview-section-delimiter"></div>

這裏寫代碼片
“`

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章