欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java 自旋鎖(spinlock)相關(guān)知識總結(jié)

 更新時間:2021年02月19日 08:58:05   作者:小小青葉  
這篇文章主要介紹了Java 自旋鎖(spinlock)相關(guān)知識總結(jié),幫助大家更好的理解和使用Java,感興趣的朋友可以了解下

一、前言

談到『自旋鎖』,可能大家會說,這有啥好講的,不就是等待資源的線程"原地打轉(zhuǎn)"嘛。嗯,字面理解的意思很到位,但能深入具體點嗎?自旋鎖的設(shè)計真就這么簡單?

本文或者說本系列的目的,都是讓大家不要停留在表面,而是深入分析,做到:

  • 靈活使用
  • 掌握原理
  • 優(yōu)缺點

二、鎖的優(yōu)化:自旋鎖

當(dāng)多個線程想同時訪問同一個資源時,就存在資源沖突,這時,大家最直接想到的就是加鎖來互斥訪問,加鎖會有這么幾個問題:

  1. 等待資源的線程進入睡眠,發(fā)生用戶態(tài)向內(nèi)核態(tài)的切換,有一定的性能開銷;
  2. 占用資源的線程很快就用完并釋放,這時等待的線程被喚醒,又要立即切換回用戶態(tài);

那么,如果有一種方式,使得等待的線程先短暫的等待一會兒,有可能有兩種結(jié)果:

  1. 等待的時間超過了這一會兒,那沒辦法,只好進入睡眠;
  2. 等待的時間還未超過,占用資源的線程釋放了,這時等待的線程就可以直接占用資源。

這就是鎖的小優(yōu)化:自旋鎖! 自旋鎖并不是真正的鎖,而是讓等待的線程先原地"小轉(zhuǎn)"一下,小轉(zhuǎn)一下,通常小轉(zhuǎn)一下的實現(xiàn)方式很簡單:

int SPIN_LOCK_NUM = 64;
int i = 0;
boolean wait = true;

do {
 wait = // 嘗試獲取資源鎖
} while (wait && (++i) < SPIN_LOCK_NUM);

我們通過循環(huán)一定的次數(shù)來自旋。 \color{red}{但是我們也應(yīng)該知道,不進入休眠而原地打轉(zhuǎn),是會一直消耗 CPU 資源的,因此,才有了自旋限制!}但是我們也應(yīng)該知道,不進入休眠而原地打轉(zhuǎn),是會一直消耗CPU資源的,因此,才有了自旋限制!

看下面的JDK源碼:

public final class Unsafe {
 public final int getAndSetInt(Object var1, long var2, int var4) {
  int var5;
  do {
   var5 = this.getIntVolatile(var1, var2);
  } while(!this.compareAndSwapInt(var1, var2, var5, var4));
 
  return var5;
 }
}

我們可以看到,CAS就是采用的自旋鎖方式,持續(xù)的嘗試讀取最新的 volatile 修飾的變量的值,并嘗試去用期望的值去比較,然后更新。

不過這里我們要注意,因為是無限循環(huán),因此我們要保證占用資源的線程很快就能釋放,而不是長時間占用(當(dāng)然,因為這里的源碼系統(tǒng)也設(shè)定了 int 型變量,因此,占用該變量的線程很快就會使用完而釋放)。

三、自旋鎖的死鎖

啥?怎么會有死鎖? 自旋鎖雖然好用,若我們只是停留在上面的分析,那么還是很膚淺的;雖然自旋鎖有很大的優(yōu)勢,但同樣缺點也不少,除了上面說的,原地打轉(zhuǎn)(忙等待)會一直消耗CPU資源,同時,還會有一個潛在的可能缺陷:死鎖。

3.1、系統(tǒng)中斷

在聊死鎖之前,我們需要先了解一下系統(tǒng)中斷事件(大學(xué)課本里有這一章節(jié),ASM匯編中也涉及到系統(tǒng)中斷向量表):

中斷是指,CPU正常運行期間,由于有內(nèi)/外部事件,或者由程序預(yù)先安排的事件,引起CPU暫停當(dāng)前工作,轉(zhuǎn)而去處理該事件,當(dāng)處理完該事件后再返回繼續(xù)運行被中斷(暫停)的程序。通常,操作系統(tǒng)將中斷分為兩類:外部中斷(硬件中斷)和內(nèi)部中斷(異常中斷,即軟件引起的);

例如:由IO設(shè)備引起的中斷為硬件中斷,比如,鍵盤輸入,硬盤/光驅(qū)讀寫等;異常中斷很好理解,比如 NullPointerException 等。

3.2、中斷處理程序

系統(tǒng)提供了一個API使得我們的程序能夠向系統(tǒng)申請注冊一個中斷處理程序(例如:程序接收用戶的輸入事件)。

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)

參數(shù)含義如下:

  • irq: 中斷號,系統(tǒng)定義好,具體可查看中斷向量表;
  • handler: 中斷后發(fā)生的ISR(Interrupt Service Routines),直接翻譯為:中斷服務(wù)路由;實際類似,是響應(yīng)中斷服務(wù)的程序;
  • flags: 中斷標(biāo)志;
  • name: 中斷相關(guān)的設(shè)備的ASCII,如:"keyboard",這些名字會在 /proc/irq 和 /proc/interrupts 中使用;
  • dev: 用于共享中斷線,傳遞驅(qū)動程序的設(shè)備結(jié)構(gòu)。非共享類型的中斷,直接設(shè)置成為 NULL

中斷標(biāo)志(flags):

  • IRQF_DISABLED: 內(nèi)核處理該ISR期間,禁止其它中斷(一般很少使用);
  • IRQF_SAMPLE_RANDOM: 表明該設(shè)備產(chǎn)生的中斷對內(nèi)核熵池有貢獻;
  • IRQF_TIMER: 系統(tǒng)定時器;
  • IRQF_SHARED: 多個ISR共享中斷線,即一個中斷,可存在多個ISR;

調(diào)用 request_irq 成功時返回0,常見錯誤是 -EBUSY,表示給定的中斷線已經(jīng)在使用(沒有指定IRQF_SHARED)。

注:

  1. 該函數(shù)可能引起睡眠,所以不允許在中斷上下文或者不允許睡眠的程序中使用!
  2. Linux 中的中斷處理程序是無須重入的。當(dāng)給定的中斷處理程序正在執(zhí)行的時候,其中斷線在所有的處理器上都會被屏蔽掉,以防在同一個中斷線上又接收到另一個新的中斷。通常情況下,除了該中斷的其他中斷都是打開的,也就是說其他的中斷線上的重點都能夠被處理,但是當(dāng)前的中斷線總是被禁止的,故,同一個中斷處理程序是絕對不會被自己嵌套的。

那這和死鎖有何關(guān)系呢?額,下一小節(jié)會談到。但這里之所有提到中斷,是因為我們還要知道一件事,當(dāng)系統(tǒng)產(chǎn)生中斷,程序被暫停時,程序是不能進入休眠的,此時程序只能采用一種方式:自旋,來保證不會睡眠。

為何不能睡眠?這里就涉及到『中斷上下文 context』!

3.3、中斷上下文 Context

上面說了,request_irq 可能引起睡眠,所以不允許在中斷上下文中使用,也就是說,中斷上下文不允許睡眠!

中斷上下文:它與進程上下文不一樣,中斷上下文是內(nèi)核正在執(zhí)行ISR。ISR沒有自己獨立的棧,而是使用內(nèi)核棧,大小一般是有限制的(32位是8KB大小)。同時,ISR是打斷了正常的程序流程,因此必須保證ISR執(zhí)行速度快。正因為要執(zhí)行速度快,所以,中斷上下文不允許睡眠,且不允許被阻塞!

大家可能會說了,執(zhí)行速度快不允許睡眠,這解釋不合理,我睡眠個1ms不行么?嗯,下面我們就來分析下不能睡眠的真正原因:

1.中斷處理時,不會發(fā)生進程切換。

  • 因為能打斷當(dāng)前中斷的只可能是更高優(yōu)先級的中斷,其它進程的優(yōu)先級是不會比中斷優(yōu)先級更高的;
  • 如果中斷上下文休眠,則沒有辦法喚醒它,因為所有的 wake_up_xxx 是針對進程而言,而中斷沒有進程的概念;
  • 只要是中斷(硬中or軟中,不是香煙),都發(fā)生在內(nèi)核,如果中斷上下文睡眠了,內(nèi)核就阻塞了,系統(tǒng)能阻塞么?不能!阻塞了你就只能重啟機器了;

2.schedule 在切換進程時,會保存當(dāng)前的進程上下文(CPU寄存器的值、狀態(tài)、堆棧SP內(nèi)容)以便以后恢復(fù)再運行。中斷發(fā)生后,內(nèi)核會保存當(dāng)前被中斷進程的上下文。在ISR中,是中斷上下文,如果休眠或阻塞,則會調(diào)用 schedule,保存的進程上下文不是當(dāng)前進程的上下文,所以不能在ISR中調(diào)用 schedule;
3.內(nèi)核中 schedule 在進入時會判斷是否處于中斷上下文:

if(unlikely(in_interrupt()))) ..... crash!!!

4.中斷 handler 會使用被中斷的進程內(nèi)核堆棧,但不會對其有任何影響,因為 handler用之前會保存,用完后會清除并恢復(fù)原貌;
5.處理中斷上下文中,內(nèi)核是不可搶占的,如果休眠,則內(nèi)核....一定會被掛起,同樣,你只能重啟機器了;
所以,被中斷的程序也不能睡眠!那么只能使用『自旋鎖』來原地打轉(zhuǎn)。

那還是沒有說自旋為何會死鎖?

自旋鎖是不能遞歸,否則自己等待自己已經(jīng)獲取的鎖,將會導(dǎo)致死鎖!

一個線程獲取了一個自旋鎖,在執(zhí)行這程中被中斷處理程序打斷,因此該線程只是暫停執(zhí)行,并未退出,仍持有自旋鎖;而中斷處理程序嘗試獲取自旋鎖而獲取不到,只能自旋;這就造成一個事實:ISR拿不到自旋鎖,導(dǎo)致自旋而無法退出,該線程被中斷無法恢復(fù)執(zhí)行至退出釋放自旋鎖,此時就造成了死鎖,導(dǎo)致系統(tǒng)崩潰。

四、死鎖解決

發(fā)生自旋鎖死鎖,往往因為單CPU這個臨界資源發(fā)生了搶占,使得一方持有自旋鎖被中斷暫停,一方不斷自旋來嘗試獲取自旋鎖。因此,在多CPU架構(gòu)下,兩方如果分別運行在不同CPU上,是不會發(fā)生死鎖的。

因此,自旋鎖有幾個重要特性需要掌握(精髓):

  • 持有自旋鎖的線程(此時肯定在臨界區(qū))不能休眠,休眠會引起進程切換,CPU就會被另一個進程占用等無法使用;
  • 持有自旋鎖的線程不允許被中斷,哪怕是ISR也不行,否則就存在ISR自旋;
  • 持有自旋鎖的線程,其內(nèi)核不能被搶占,否則等同于CPU被搶占;

所以,根據(jù)以上總結(jié)一點:持有自旋鎖的線程,不能因為任何原因而放棄CPU! 也因此基于上述問題,自旋也需要添加一個上限時間以防死鎖。

linux上的自旋鎖有三種實現(xiàn):

  1. 在單cpu,不可搶占內(nèi)核中,自旋鎖為空操作。
  2. 在單cpu,可搶占內(nèi)核中,自旋鎖實現(xiàn)為“禁止內(nèi)核搶占”,并不實現(xiàn)“自旋”。(注意)
  3. 在多cpu,可搶占內(nèi)核中,自旋鎖實現(xiàn)為“禁止內(nèi)核搶占” + “自旋”。

以上就是Java 自旋鎖(spinlock)相關(guān)知識總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Java 自旋鎖(spinlock)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • JavaMail實現(xiàn)郵件發(fā)送的方法

    JavaMail實現(xiàn)郵件發(fā)送的方法

    這篇文章主要介紹了JavaMail實現(xiàn)郵件發(fā)送的方法,實例分析了java實現(xiàn)郵件發(fā)送的相關(guān)技巧,非常具有實用價值,需要的朋友可以參考下
    2015-04-04
  • Java設(shè)計模式之中介者模式(Mediator Pattern)簡介

    Java設(shè)計模式之中介者模式(Mediator Pattern)簡介

    這篇文章主要介紹了Java設(shè)計模式之中介者模式(Mediator Pattern),需要的朋友可以參考下
    2014-07-07
  • Java線程的五種狀態(tài)介紹

    Java線程的五種狀態(tài)介紹

    本文主要為大家詳細(xì)介紹一下Java實現(xiàn)線程創(chuàng)建的五種寫法,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)有一定的幫助,感興趣的可以跟隨小編學(xué)習(xí)一下
    2022-08-08
  • 程序員最喜歡的ThreadLocal使用姿勢

    程序員最喜歡的ThreadLocal使用姿勢

    ThreadLocal并不是一個Thread,而是Thread的局部變量,也許把它命名為ThreadLocalVariable更容易讓人理解一些,下面這篇文章主要給大家介紹了程序員最喜歡的ThreadLocal使用姿勢,需要的朋友可以參考下
    2022-02-02
  • Java矢量隊列Vector使用示例

    Java矢量隊列Vector使用示例

    Vector類實現(xiàn)了一個動態(tài)數(shù)組。和ArrayList很相似,但是兩者是不同的Vector是同步訪問的;Vector包含了許多傳統(tǒng)的方法,這些方法不屬于集合框架
    2023-01-01
  • java.net.ConnectException: Connection refused問題解決辦法

    java.net.ConnectException: Connection refused問題解決辦法

    這篇文章主要介紹了java.net.ConnectException: Connection refused問題解決辦法的相關(guān)資料,需要的朋友可以參考下
    2016-12-12
  • 淺談MyBatis所有的jdbcType類型

    淺談MyBatis所有的jdbcType類型

    在Mybatis中JdbcType類型是一個枚舉類型,它包含了所有的JDBC數(shù)據(jù)類型,如VARCHAR、INTEGER、DATE等,本文主要介紹了淺談MyBatis所有的jdbcType類型,具有一定的參考價值,感興趣的可以了解一下
    2023-06-06
  • Java日常練習(xí)題,每天進步一點點(52)

    Java日常練習(xí)題,每天進步一點點(52)

    下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你
    2021-08-08
  • java 如何將多種字符串格式 解析為Date格式

    java 如何將多種字符串格式 解析為Date格式

    這篇文章主要介紹了java 如何將多種字符串格式 解析為Date格式的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • MyBatis持久層框架的用法知識小結(jié)

    MyBatis持久層框架的用法知識小結(jié)

    MyBatis 本是apache的一個開源項目iBatis,接下來通過本文給大家介紹MyBatis持久層框架的用法知識小結(jié),非常不錯,具有參考借鑒價值,感興趣的朋友一起學(xué)習(xí)吧
    2016-07-07

最新評論