一、引子
如果我問你在Java語言環境下何時使用CAS機制,你可能會說:出現線程不安全可能性的時候就是我們應當使用CAS機制的時候。但是這個說話雖然是正确的,但是太籠統以至于說了好像沒說一樣。如果你學過
synchronized
關鍵字,你一定知道同步機制帶來的記憶體上的損耗是很大的,比如頻繁的上下文切換就是我們在使用
synchronized
關鍵字時急需避免的。但是如果你了解CAS機制的話,你就會知道此機制有可能會導緻線程占據CPU資源,如果線上程安全的條件下仍然使用CAS機制,那麼就會帶來不必要的CPU資源損耗。
二、何時使用CAS機制
首先給出使用CAS機制的原則:
- 線程之間搶占資源不是特别激烈使用CAS機制,這保證了大部分線程不會是在幹等資源的釋放
- 等待資源釋放時的CPU占用反而小于上下文切換所消耗的資源,使用CAS機制
- 線程可能出現不安全情況的條件下才使用CAS機制
解釋:
- CAS機制由于往往和自鎖(
)機制相結合使用,是以在自旋機制下,線程競争越激烈,越多的線程在循環中等待資源釋放,而這個過程是占據CPU資源的for(;;)
- 第二點的内涵是:我們需要確定
關鍵字性能比CAS機制差synchronized
- 第三點的解釋看似平常,但是卻是我們平常不關注的地方,以下我們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機制使用處可能出現線程不安全情況,而後者卻是一定處于線程安全情況。下面來說說具體的判斷原因:
- 首先說明上述代碼塊的鎖特性:上述鎖結構是一個獨占鎖,隻允許一個線程占據鎖資源,但是允許一個線程多次占據鎖資源(重入);
- 當鎖資源沒有被任何線程占據,那麼可能出現多個線程同時去搶占鎖資源的情況,此時線程顯然是不安全的,是以需要使用CAS機制來進行線程安全性的保證,并且多個搶占資源的線程中隻有一個線程會搶占到所資源,是以将其放置于
中,隻有成功的線程才會被設定為目前鎖對象的獨占線程;if邏輯判斷語句
- 而後者調用普通的set方法原因是:允許重入鎖的條件是占據鎖資源的線程恰好為目前通路鎖對象的線程,這樣的線程有且隻有一個,那麼進行狀态更新時,就相當于我們尚未學習多線程知識前單線程的set方法,無須考慮線程不安全性,那麼就無須使用CAS機制。
三、小結
從CAS機制使用原則上我們還是可以看出一點,如果能笃定地根據代碼邏輯判斷出目前代碼塊是被單線程通路或者執行的,那麼我們應當堅決擁護最簡單的單線程中的寫方法。不是說在學習好多線程知識之後我們在何時何處都應當使用多線程的寫方法來保護線程安全性。如果多線程帶來線程安全性保障是不必要的,那麼多線程導緻的額外損耗就是多餘。