什么是自旋鎖



多線程中,對共享資源進行訪問,為了防止並發引起的相關問題,通常都是引入鎖的機制來處理並發問題。

獲取到資源的線程A對這個資源加鎖,其他線程比如B要訪問這個資源首先要獲得鎖,而此時A持有這個資源的鎖,只有等待線程A邏輯執行完,釋放鎖,這個時候B才能獲取到資源的鎖進而獲取到該資源。

這個過程中,A一直持有着資源的鎖,那么沒有獲取到鎖的其他線程比如B怎么辦?通常就會有兩種方式:

1. 一種是沒有獲得鎖的進程就直接進入阻塞(BLOCKING),這種就是互斥鎖

2. 另外一種就是沒有獲得鎖的進程,不進入阻塞,而是一直循環着,看是否能夠等到A釋放了資源的鎖。

上述的兩種方式,學術上,就有幾種不同的定義方式,大學的時候 學習的是C++, 《C++ 11》中就有這樣的描述:

自旋鎖(spin lock)是一種非阻塞鎖,也就是說,如果某線程需要獲取鎖,但該鎖已經被其他線程占用時,該線程不會被掛起,而是在不斷的消耗CPU的時間,不停的試圖獲取鎖。

互斥量(mutex)是阻塞鎖,當某線程無法獲取鎖時,該線程會被直接掛起,該線程不再消耗CPU時間,當其他線程釋放鎖后,操作系統會激活那個被掛起的線程,讓其投入運行。

而《linux內核設計與實現》經常提到兩種態,一種是內核態,一種是用戶態,對於自旋鎖來說,自旋鎖使線程處於用戶態,而互斥鎖需要重新分配,進入到內核態。這里大家對內核態和用戶態有個初步的認知就行了,用戶態比較輕,內核態比較重。用戶態和內核態這個也是linux中必備的知識基礎,借鑒這個,可以進行很多程序設計語言API上的優化,就比如說javaio的部分,操作io的時候,先是要從用戶態,進入內核態,再用內核態去操作輸入輸出設備的抽象,這里減少用戶態到內核態的轉換就是新io的一部分優化,后面再聊。

wiki中的定義如下:

自旋鎖是計算機科學用於多線程同步的一種鎖,線程反復檢查鎖變量是否可用。由於線程在這一過程中保持執行,因此是一種忙等待。

自旋鎖避免了進程上下文的調度開銷,因此對於線程只會阻塞很短時間的場合是有效的。因此操作系統的實現在很多地方往往用自旋鎖。Windows操作系統提供的輕型讀寫鎖(SRW Lock)內部就用了自旋鎖。顯然,單核CPU不適於使用自旋鎖,這里的單核CPU指的是單核單線程的CPU,因為,在同一時間只有一個線程是處在運行狀態,假設運行線程A發現無法獲取鎖,只能等待解鎖,但因為A自身不掛起,所以那個持有鎖的線程B沒有辦法進入運行狀態,只能等到操作系統分給A的時間片用完,才能有機會被調度。這種情況下使用自旋鎖的代價很高。(紅字部分是我給wiki編輯的詞條,單核CPU不適合自旋鎖,這個也只是針對單核單線程的情況,現在的技術基本單核都是支持多線程的)

為什么要使用自旋鎖
互斥鎖有一個缺點,他的執行流程是這樣的 托管代碼  - 用戶態代碼 - 內核態代碼、上下文切換開銷與損耗,假如獲取到資源鎖的線程A立馬處理完邏輯釋放掉資源鎖,如果是采取互斥的方式,那么線程B從沒有獲取鎖到獲取鎖這個過程中,就要用戶態和內核態調度、上下文切換的開銷和損耗。所以就有了自旋鎖的模式,讓線程B就在用戶態循環等着,減少消耗。

自旋鎖比較適用於鎖使用者保持鎖時間比較短的情況,這種情況下自旋鎖的效率要遠高於互斥鎖。

自旋鎖可能潛在的問題
過多占用CPU的資源,如果鎖持有者線程A一直長時間的持有鎖處理自己的邏輯,那么這個線程B就會一直循環等待過度占用cpu資源
遞歸使用可能會造成死鎖,不過這種場景一般寫不出來
CAS
就不寫術語定義了,簡單的理解就是這個CAS是由操作系統定義的,由若干指令組成的,這個操作具有原子性,這些指令如果執行,就會全部執行完,不會被中斷。CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。

CAS的問題
經典的CAS的ABA問題,上面提到了CAS操作的時候,要檢測值有沒有變化,如果一個值原來是A,后來變成了B, 后來又變成了A,CAS會認為沒有發生變化。
       解決方案:

       1. 加版本號   1A - 2B -  3A

        2. 對java而言,jdk1.5提供了AtomicStampedReference來解決這個問題

只能保證一個共享變量的原子操作 
        CAS通常是對一個變量來進行原子操作的,所以如果對多個變量進行原子操作就會有問題了。

        解決方案

        1. 簡單粗暴,加鎖,反而加入了復雜性,最low的方式

        2. 跟上面的加版本號的道理一樣,就是將多個變量拼成一個變量(可以拼成一個字符串)

        3. 對java而言,jdk1.5 提供了AtomicStampedReference,這個reference 就是個對象引用,把多個變量放在這個對象里即可

JAVA CAS封裝
 sun.misc.Unsafe是JDK里面的一個內部類,這個類當中有三個CAS的操作

JAVA自旋鎖應用
Jdk1.5以后,提供了java.util.concurrent.atomic包,這個包里面提供了一組原子類。基本上就是當前獲取鎖的線程,執行更新的方法,其他線程自旋等待,比如atomicInteger類中的getAndAdd方法內部實際上使用的就是Unsafe的方法。

/**
* Atomically adds the given value to the current value.
*
* @param delta the value to add
* @return the previous value
*/
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
當然java中的syncronized關鍵字,在1.5中有了很大的優化,加入了偏隙鎖也有人叫偏向鎖,主要的實現方式就是在對象頭markword中打上線程的信息,這樣資源上的鎖的獲取就偏向了這個線程,后面,會涉及一系列的鎖升級的問題,間隙鎖 - 輕量鎖 - 重量級鎖 ,鎖升級后面單獨抽出來寫一篇,這個輕量鎖實際上就是使用的也是自旋鎖的實現方式。
---------------------
作者:csucoderlee
來源:CSDN
原文:https://blog.csdn.net/u010372981/article/details/81463201
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM