Java併發-什麼時候使用CAS機制

一、引子

 如果我問你在Java語言環境下何時使用CAS機制,你可能會說:出現線程不安全可能性的時候就是我們應當使用CAS機制的時候。但是這個說話雖然是正確的,但是太籠統以至於說了好像沒說一樣。如果你學過synchronized關鍵字,你一定知道同步機制帶來的內存上的損耗是很大的,比如頻繁的上下文切換就是我們在使用synchronized關鍵字時急需避免的。但是如果你瞭解CAS機制的話,你就會知道此機制有可能會導致線程佔據CPU資源,如果在線程安全的條件下仍然使用CAS機制,那麼就會帶來不必要的CPU資源損耗。

二、何時使用CAS機制

首先給出使用CAS機制的原則:

  1. 線程之間搶佔資源不是特別激烈使用CAS機制,這保證了大部分線程不會是在乾等資源的釋放
  2. 等待資源釋放時的CPU佔用反而小於上下文切換所消耗的資源,使用CAS機制
  3. 線程可能出現不安全情況的條件下才使用CAS機制

解釋:

  1. CAS機制由於往往和自鎖(for(;;))機制相結合使用,所以在自旋機制下,線程競爭越激烈,越多的線程在循環中等待資源釋放,而這個過程是佔據CPU資源的
  2. 第二點的內涵是:我們需要確保synchronized關鍵字性能比CAS機制差
  3. 第三點的解釋看似平常,但是卻是我們平常不關注的地方,以下我們JDK源代碼做解釋:
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//得到訪問鎖對象的當前線程對象
            int c = getState();//得到當前鎖對象的狀態
            if (c == 0) {	//狀態爲0,意味着沒有任何線程佔據着當前鎖對象
                if (compareAndSetState(0, acquires)) {//使用CAS機制將當前鎖狀態更新,只有一個線程會成功,返回true
                    setExclusiveOwnerThread(current);//將當前線程置爲鎖的獨佔線程
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//如果當前線程卡位佔據鎖對象的線程
                int nextc = c + acquires;//得到當前線程重入鎖後的狀態
                if (nextc < 0) // overflow//這是鎖狀態的非法值,如若此值,則拋出異常
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//調用set方法,更新狀態值。
                return true;
            }
            return false;
        }

 以上代碼是java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire中所定義的當前線程嘗試獲取資源的方法,可能你還沒有學過AQS機制,Lock接口,但是通過我上述對代碼的註釋,相信你應該對這個代碼塊可以有一個大致的認識。
 不知道你有沒有注意到一點,上述代碼有兩處用不同的方法進行鎖狀態的更新
if (compareAndSetState(0, acquires)) 以及setState(nextc);
 但是爲何目的都是鎖對象狀態更新,實現方式卻是一個CAS機制,一個普通的set方法。
原因是上述原則中的第三點:CAS機制使用處可能出現線程不安全情況,而後者卻是一定處於線程安全情況。下面來說說具體的判斷原因:

  1. 首先說明上述代碼塊的鎖特性:上述鎖結構是一個獨佔鎖,只允許一個線程佔據鎖資源,但是允許一個線程多次佔據鎖資源(重入);
  2. 當鎖資源沒有被任何線程佔據,那麼可能出現多個線程同時去搶佔鎖資源的情況,此時線程顯然是不安全的,所以需要使用CAS機制來進行線程安全性的保證,並且多個搶佔資源的線程中只有一個線程會搶佔到所資源,所以將其放置於if邏輯判斷語句中,只有成功的線程纔會被設置爲當前鎖對象的獨佔線程;
  3. 而後者調用普通的set方法原因是:允許重入鎖的條件是佔據鎖資源的線程恰好爲當前訪問鎖對象的線程,這樣的線程有且只有一個,那麼進行狀態更新時,就相當於我們尚未學習多線程知識前單線程的set方法,無須考慮線程不安全性,那麼就無須使用CAS機制。

三、小結

 從CAS機制使用原則上我們還是可以看出一點,如果能篤定地根據代碼邏輯判斷出當前代碼塊是被單線程訪問或者執行的,那麼我們應當堅決擁護最簡單的單線程中的寫方法。不是說在學習好多線程知識之後我們在何時何處都應當使用多線程的寫方法來保護線程安全性。如果多線程帶來線程安全性保障是不必要的,那麼多線程導致的額外損耗就是多餘。

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