Java自旋鎖及自旋的好處詳解
什么是自旋
我們了解什么叫自旋?“自旋”可以理解為“自我旋轉(zhuǎn)”,這里的“旋轉(zhuǎn)”指“循環(huán)”,比如 while 循環(huán)或者 for 循環(huán)。“自旋”就是自己在這里不停地循環(huán),直到目標達成。而不像普通的鎖那樣,如果獲取不到鎖就進入阻塞。
對比自旋和非自旋的獲取鎖的流程
我們用這樣一張流程圖來對比一下自旋鎖和非自旋鎖的獲取鎖的過程。
我們來看自旋鎖,它并不會放棄 CPU 時間片,而是通過自旋等待鎖的釋放,也就是說,它會不停地再次地嘗試獲取鎖,如果失敗就再次嘗試,直到成功為止。
我們再來看下非自旋鎖,非自旋鎖和自旋鎖是完全不一樣的,如果它發(fā)現(xiàn)此時獲取不到鎖,它就把自己的線程切換狀態(tài),讓線程休眠,然后 CPU 就可以在這段時間去做很多其他的事情,直到之前持有這把鎖的線程釋放了鎖,于是 CPU 再把之前的線程恢復回來,讓這個線程再去嘗試獲取這把鎖。如果再次失敗,就再次讓線程休眠,如果成功,一樣可以成功獲取到同步資源的鎖。
可以看出,非自旋鎖和自旋鎖最大的區(qū)別,就是如果它遇到拿不到鎖的情況,它會把線程阻塞,直到被喚醒。而自旋鎖會不停地嘗試。那么,自旋鎖這樣不停嘗試的好處是什么呢?
自旋鎖的好處
阻塞和喚醒線程都是需要高昂的開銷的,如果同步代碼塊中的內(nèi)容不復雜,那么可能轉(zhuǎn)換線程帶來的開銷比實際業(yè)務(wù)代碼執(zhí)行的開銷還要大。
在很多場景下,可能我們的同步代碼塊的內(nèi)容并不多,所以需要的執(zhí)行時間也很短,如果我們僅僅為了這點時間就去切換線程狀態(tài),那么其實不如讓線程不切換狀態(tài),而是讓它自旋地嘗試獲取鎖,等待其他線程釋放鎖,有時我只需要稍等一下,就可以避免上下文切換等開銷,提高了效率。
用一句話總結(jié)自旋鎖的好處,那就是自旋鎖用循環(huán)去不停地嘗試獲取鎖,讓線程始終處于 Runnable 狀態(tài),節(jié)省了線程狀態(tài)切換帶來的開銷。
AtomicLong 的實現(xiàn)
在 Java 1.5 版本及以上的并發(fā)包中,也就是 java.util.concurrent 的包中,里面的原子類基本都是自旋鎖的實現(xiàn)。
比如我們看一個 AtomicLong 的實現(xiàn),里面有一個 getAndIncrement 方法,源碼如下:
public final long getAndIncrement() { return unsafe.getAndAddLong(this, valueOffset, 1L); }
可以看到它調(diào)用了一個 unsafe.getAndAddLong,所以我們再來看這個方法:
public final long getAndAddLong (Object var1,long var2, long var4){ long var6; do { var6 = this.getLongVolatile(var1, var2); } while (!this.compareAndSwapLong(var1, var2, var6, var6 + var4)); return var6; }
在這個方法中,它用了一個 do while 循環(huán)。這里就很明顯了:
do { var6 = this.getLongVolatile(var1, var2); } while (!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
這里的 do-while 循環(huán)就是一個自旋操作,如果在修改過程中遇到了其他線程競爭導致沒修改成功的情況,就會 while 循環(huán)里進行死循環(huán),直到修改成功為止。
自己實現(xiàn)一個可重入的自旋鎖
下面我們來看一個自己實現(xiàn)可重入的自旋鎖。
代碼如下所示:
package lesson27; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; /** * 描述: 實現(xiàn)一個可重入的自旋鎖 */ public class ReentrantSpinLock { private AtomicReference<Thread> owner = new AtomicReference<>(); //重入次數(shù) private int count = 0; public void lock() { Thread t = Thread.currentThread(); if (t == owner.get()) { ++count; return; } //自旋獲取鎖 while (!owner.compareAndSet(null, t)) { System.out.println("自旋了"); } } public void unlock() { Thread t = Thread.currentThread(); //只有持有鎖的線程才能解鎖 if (t == owner.get()) { if (count > 0) { --count; } else { //此處無需CAS操作,因為沒有競爭,因為只有線程持有者才能解鎖 owner.set(null); } } } public static void main(String[] args) { ReentrantSpinLock spinLock = new ReentrantSpinLock(); Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "開始嘗試獲取自旋鎖"); spinLock.lock(); try { System.out.println(Thread.currentThread().getName() + "獲取到了自旋鎖"); Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } finally { spinLock.unlock(); System.out.println(Thread.currentThread().getName() + "釋放了了自旋鎖"); } } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); } }
這段代碼的運行結(jié)果是:
...
自旋了
自旋了
自旋了
自旋了
自旋了
自旋了
自旋了
自旋了
Thread-0釋放了了自旋鎖
Thread-1獲取到了自旋鎖
前面會打印出很多“自旋了”,說明自旋期間,CPU依然在不停運轉(zhuǎn)。
缺點
那么自旋鎖有沒有缺點呢?其實自旋鎖是有缺點的。它最大的缺點就在于雖然避免了線程切換的開銷,但是它在避免線程切換開銷的同時也帶來了新的開銷,因為它需要不停得去嘗試獲取鎖。如果這把鎖一直不能被釋放,那么這種嘗試只是無用的嘗試,會白白浪費處理器資源。也就是說,雖然一開始自旋鎖的開銷低于線程切換,但是隨著時間的增加,這種開銷也是水漲船高,后期甚至會超過線程切換的開銷,得不償失。
適用場景
所以我們就要看一下自旋鎖的適用場景。首先,自旋鎖適用于并發(fā)度不是特別高的場景,以及臨界區(qū)比較短小的情況,這樣我們可以利用避免線程切換來提高效率。
可是如果臨界區(qū)很大,線程一旦拿到鎖,很久才會釋放的話,那就不合適用自旋鎖,因為自旋會一直占用 CPU 卻無法拿到鎖,白白消耗資源。
到此這篇關(guān)于Java自旋鎖及自旋的好處詳解的文章就介紹到這了,更多相關(guān)Java自旋鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使Java的JButton文字隱藏功能的實現(xiàn)(不隱藏按鈕的前提)
這篇文章主要介紹了使Java的JButton文字隱藏功能的實現(xiàn)(不隱藏按鈕的前提),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01SpringBoot配置文件方式,在線yml文件轉(zhuǎn)properties
這篇文章主要介紹了SpringBoot配置文件方式,在線yml文件轉(zhuǎn)properties,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07RabbitMQ排他性隊列Exclusive Queue詳解
這篇文章主要介紹了RabbitMQ排他性隊列Exclusive Queue詳解,如果你想創(chuàng)建一個只有自己可見的隊列,即不允許其它用戶訪問,RabbitMQ允許你將一個Queue聲明成為排他性的Exclusive Queue,需要的朋友可以參考下2023-08-08Java使用ant.jar執(zhí)行SQL腳本文件的示例代碼
這篇文章主要介紹了Java使用ant.jar執(zhí)行SQL腳本文件,文中通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2024-02-02