Java中的自旋鎖解析
一、自旋鎖介紹
什么是自旋鎖
自旋鎖是指當(dāng)一個線程嘗試獲取某個鎖時,如果該鎖已被其他線程占用,就一直循環(huán)檢測鎖是否被釋放,而不是進(jìn)入線程掛起或睡眠狀態(tài)。
為什么要使用自旋鎖
多個線程對同一個變量一直使用CAS操作,那么會有大量修改操作,從而產(chǎn)生大量的緩存一致性流量,因?yàn)槊恳淮蜟AS操作都會發(fā)出廣播通知其他處理器,從而影響程序的性能。
線程自旋與線程阻塞
阻塞的缺點(diǎn)顯而易見,線程一旦進(jìn)入阻塞(Block),再被喚醒的代價(jià)比較高,性能較差。自旋的優(yōu)點(diǎn)是線程還是Runnable的,只是在執(zhí)行空代碼。當(dāng)然一直自旋也會白白消耗計(jì)算資源,所以常見的做法是先自旋一段時間,還沒拿到鎖就進(jìn)入阻塞。JVM在處理synchrized實(shí)現(xiàn)時就是采用了這種折中的方案,并提供了調(diào)節(jié)自旋的參數(shù)。
首先來對比一下互斥鎖和自旋鎖。
- 互斥鎖:從等待到解鎖過程,線程會從block狀態(tài)變?yōu)閞unning狀態(tài),過程中有線程上下文的切換,搶占CPU等開銷。
- 自旋鎖:從等待到解鎖過程,線程一直處于running狀態(tài),沒有上下文的切換。
雖然自旋鎖效率比互斥鎖高,但它會存在下面兩個問題
- 自旋鎖一直占用CPU,在未獲得鎖的情況下,一直運(yùn)行,如果不能在很短的時間內(nèi)獲得鎖,會導(dǎo)致CPU效率降低。
- 試圖遞歸地獲得自旋鎖會引起死鎖。遞歸程序決不能在持有自旋鎖時調(diào)用它自己,也決不能在遞歸調(diào)用時試圖獲得相同的自旋鎖。
由此可見,我們要慎重的使用自旋鎖,自旋鎖適合于鎖使用者保持鎖時間比較短并且鎖競爭不激烈的情況。正是由于自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠(yuǎn)高于互斥鎖。
二、代碼舉例
/**
* @author lichangyuan
* @create 2021-10-08 10:50
*/
public class SpinLock {
public static void main(String[] args) throws InterruptedException {
//設(shè)置100容量線程池
ExecutorService executorService = Executors.newFixedThreadPool(100);
//計(jì)數(shù)器用于阻塞
CountDownLatch countDownLatch = new CountDownLatch(10);
//創(chuàng)建自旋鎖對象
SimpleSpinningLock simpleSpinningLock = new SimpleSpinningLock();
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
simpleSpinningLock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子線程:" + Thread.currentThread().getName() + "執(zhí)行");
simpleSpinningLock.unLock();
//確認(rèn)已經(jīng)連接完畢后再進(jìn)行操作,將count值減1
countDownLatch.countDown();
}
});
}
//調(diào)用await()方法的線程會被掛起,它會等待直到count值為0才繼續(xù)執(zhí)行,沒有則調(diào)用countDown則繼續(xù)阻塞
countDownLatch.await();
}
}
class SimpleSpinningLock {
/**
* 持有鎖的線程,null表示鎖未被線程持有
*/
private AtomicReference<Thread> sign = new AtomicReference<>();
/**
* 調(diào)用lock方法時,如果sign當(dāng)前值為null,說明自旋鎖還沒有被占用,將sign設(shè)置為currentThread,并進(jìn)行鎖定。
* 調(diào)用lock方法時,如果sign當(dāng)前值不為null,說明自旋鎖已經(jīng)被其他線程占用,當(dāng)前線程就會在while中繼續(xù)循環(huán)檢測。
*/
public void lock() {
//返回的正是執(zhí)行當(dāng)前代碼指令的線程引用
Thread currentThread = Thread.currentThread();
//expect:它指定原子對象應(yīng)為的值。
//val:如果原子整數(shù)等于期望值,則該值指定要更新的值。
while (!sign.compareAndSet(null, currentThread)) {
//當(dāng)ref為null的時候compareAndSet返回true,反之為false
//通過循環(huán)不斷的自旋判斷鎖是否被其他線程持有
}
}
/**
* 調(diào)用unlock方法時,會將sign置為空,相當(dāng)于釋放自旋鎖。
*/
public void unLock() {
Thread currentThread = Thread.currentThread();
//expect:它指定原子對象應(yīng)為的值。
//val:如果原子整數(shù)等于期望值,則該值指定要更新的值。
sign.compareAndSet(currentThread, null);
}
}總結(jié)
由于自旋鎖只是在當(dāng)前線程不停地執(zhí)行循環(huán)體,不進(jìn)行線程狀態(tài)的切換,因此響應(yīng)速度更快。
但當(dāng)線程數(shù)不停增加時,性能下降明顯,因?yàn)槊總€線程都需要占用CPU時間。
如果線程競爭不激烈,并且保持鎖的時間很短,則適合使用自旋鎖。
到此這篇關(guān)于Java中的自旋鎖解析的文章就介紹到這了,更多相關(guān)Java自旋鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot集成spring cache緩存示例代碼
本篇文章主要介紹了springboot集成spring cache示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05
SpringCloud負(fù)載均衡實(shí)現(xiàn)定向路由詳情
這篇文章主要介紹了SpringCloud負(fù)載均衡實(shí)現(xiàn)定向路由詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08
Spring Boot Controller處理HTTP請求體的方法
Spring Boot提供了強(qiáng)大的機(jī)制來處理不同 Content-Type? 的HTTP請求體,這主要依賴于 HttpMessageConverter? 接口的各種實(shí)現(xiàn),它們能夠自動將請求體內(nèi)容轉(zhuǎn)換成Java方法參數(shù),本文給大家介紹Spring Boot Controller處理HTTP請求體的方法,感興趣的朋友一起看看吧2025-05-05
java基礎(chǔ)之字符串編碼知識點(diǎn)總結(jié)
這篇文章主要介紹了java基礎(chǔ)之字符串編碼總結(jié),文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有很好的幫助,要的朋友可以參考下2021-04-04
Spring?Security?基于URL的權(quán)限判斷源碼解析
這篇文章主要介紹了Spring?Security?基于URL的權(quán)限判斷問題,我們想要實(shí)現(xiàn)自己的基于請求Url的授權(quán)只需自定義一個?AccessDecisionManager即可,接下來跟隨小編一起看看實(shí)現(xiàn)代碼吧2021-12-12

