Java中的自旋鎖解析
一、自旋鎖介紹
什么是自旋鎖
自旋鎖是指當一個線程嘗試獲取某個鎖時,如果該鎖已被其他線程占用,就一直循環(huán)檢測鎖是否被釋放,而不是進入線程掛起或睡眠狀態(tài)。
為什么要使用自旋鎖
多個線程對同一個變量一直使用CAS操作,那么會有大量修改操作,從而產(chǎn)生大量的緩存一致性流量,因為每一次CAS操作都會發(fā)出廣播通知其他處理器,從而影響程序的性能。
線程自旋與線程阻塞
阻塞的缺點顯而易見,線程一旦進入阻塞(Block),再被喚醒的代價比較高,性能較差。自旋的優(yōu)點是線程還是Runnable的,只是在執(zhí)行空代碼。當然一直自旋也會白白消耗計算資源,所以常見的做法是先自旋一段時間,還沒拿到鎖就進入阻塞。JVM在處理synchrized實現(xiàn)時就是采用了這種折中的方案,并提供了調節(jié)自旋的參數(shù)。
首先來對比一下互斥鎖和自旋鎖。
- 互斥鎖:從等待到解鎖過程,線程會從block狀態(tài)變?yōu)閞unning狀態(tài),過程中有線程上下文的切換,搶占CPU等開銷。
- 自旋鎖:從等待到解鎖過程,線程一直處于running狀態(tài),沒有上下文的切換。
雖然自旋鎖效率比互斥鎖高,但它會存在下面兩個問題
- 自旋鎖一直占用CPU,在未獲得鎖的情況下,一直運行,如果不能在很短的時間內獲得鎖,會導致CPU效率降低。
- 試圖遞歸地獲得自旋鎖會引起死鎖。遞歸程序決不能在持有自旋鎖時調用它自己,也決不能在遞歸調用時試圖獲得相同的自旋鎖。
由此可見,我們要慎重的使用自旋鎖,自旋鎖適合于鎖使用者保持鎖時間比較短并且鎖競爭不激烈的情況。正是由于自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高于互斥鎖。
二、代碼舉例
/** * @author lichangyuan * @create 2021-10-08 10:50 */ public class SpinLock { public static void main(String[] args) throws InterruptedException { //設置100容量線程池 ExecutorService executorService = Executors.newFixedThreadPool(100); //計數(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(); //確認已經(jīng)連接完畢后再進行操作,將count值減1 countDownLatch.countDown(); } }); } //調用await()方法的線程會被掛起,它會等待直到count值為0才繼續(xù)執(zhí)行,沒有則調用countDown則繼續(xù)阻塞 countDownLatch.await(); } } class SimpleSpinningLock { /** * 持有鎖的線程,null表示鎖未被線程持有 */ private AtomicReference<Thread> sign = new AtomicReference<>(); /** * 調用lock方法時,如果sign當前值為null,說明自旋鎖還沒有被占用,將sign設置為currentThread,并進行鎖定。 * 調用lock方法時,如果sign當前值不為null,說明自旋鎖已經(jīng)被其他線程占用,當前線程就會在while中繼續(xù)循環(huán)檢測。 */ public void lock() { //返回的正是執(zhí)行當前代碼指令的線程引用 Thread currentThread = Thread.currentThread(); //expect:它指定原子對象應為的值。 //val:如果原子整數(shù)等于期望值,則該值指定要更新的值。 while (!sign.compareAndSet(null, currentThread)) { //當ref為null的時候compareAndSet返回true,反之為false //通過循環(huán)不斷的自旋判斷鎖是否被其他線程持有 } } /** * 調用unlock方法時,會將sign置為空,相當于釋放自旋鎖。 */ public void unLock() { Thread currentThread = Thread.currentThread(); //expect:它指定原子對象應為的值。 //val:如果原子整數(shù)等于期望值,則該值指定要更新的值。 sign.compareAndSet(currentThread, null); } }
總結
由于自旋鎖只是在當前線程不停地執(zhí)行循環(huán)體,不進行線程狀態(tài)的切換,因此響應速度更快。
但當線程數(shù)不停增加時,性能下降明顯,因為每個線程都需要占用CPU時間。
如果線程競爭不激烈,并且保持鎖的時間很短,則適合使用自旋鎖。
到此這篇關于Java中的自旋鎖解析的文章就介紹到這了,更多相關Java自旋鎖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringMVC接收復雜集合對象(參數(shù))代碼示例
這篇文章主要介紹了SpringMVC接收復雜集合對象(參數(shù))代碼示例,舉接收List<String>、List<User>、List<Map<String,Object>>、User[]、User(bean里面包含List)幾種較為復雜的集合參數(shù),具有一定參考價值,需要的朋友可以了解下。2017-11-11java實現(xiàn)PPT轉PDF出現(xiàn)中文亂碼問題的解決方法
這篇文章主要為大家詳細介紹了java實現(xiàn)PPT轉PDF出現(xiàn)中文亂碼問題的解決方法,進行了詳細的問題分析,需要的朋友可以參考下2015-11-11SpringBoot攔截器實現(xiàn)登錄攔截的示例代碼
本文主要介紹了SpringBoot攔截器實現(xiàn)登錄攔截,文中根據(jù)實例編碼詳細介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03IDEA配置靜態(tài)資源熱加載操作(Springboot修改靜態(tài)資源不重啟)
這篇文章主要介紹了IDEA配置靜態(tài)資源熱加載操作(Springboot修改靜態(tài)資源不重啟),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10