終于搞懂了,悲觀鎖鳖藕、樂觀鎖魔慷、分布式都在什么場(chǎng)景下使用?有什么技巧著恩?不容易呀院尔!

如何確保一個(gè)方法,或者一塊代碼在高并發(fā)情況下喉誊,同一時(shí)間只能被一個(gè)線程執(zhí)行邀摆,單體應(yīng)用可以使用并發(fā)處理相關(guān)的 API 進(jìn)行控制,但單體應(yīng)用架構(gòu)演變?yōu)榉植际轿⒎?wù)架構(gòu)后伍茄,跨進(jìn)程的實(shí)例部署栋盹,顯然就沒辦法通過應(yīng)用層鎖的機(jī)制來控制并發(fā)了。

那么鎖都有哪些類型敷矫,為什么要使用鎖例获,鎖的使用場(chǎng)景有哪些汉额?
?

鎖類別

不同的應(yīng)用場(chǎng)景對(duì)鎖的要求各不相同,我們先來看下鎖都有哪些類別榨汤,這些鎖之間有什么區(qū)別蠕搜。

  • 悲觀鎖(synchronize)

  • Java 中的重量級(jí)鎖 synchronize

  • 數(shù)據(jù)庫(kù)行鎖

  • 樂觀鎖

  • Java 中的輕量級(jí)鎖 volatile 和 CAS

  • 數(shù)據(jù)庫(kù)版本號(hào)

  • 分布式鎖(Redis鎖)

樂觀鎖

就好比說是你是一個(gè)生活態(tài)度樂觀積極向上的人,總是往最好的情況去想收壕,比如你每次去獲取共享數(shù)據(jù)的時(shí)候會(huì)認(rèn)為別人不會(huì)修改妓灌,所以不會(huì)上鎖,但是在更新的時(shí)候你會(huì)判斷這期間有沒有人去更新這個(gè)數(shù)據(jù)蜜宪。

關(guān)注公眾號(hào):程序員白楠楠虫埂, 即可獲取2020年末總結(jié)Java面試資料題一套。

樂觀鎖使用在前端壳,判斷在后告丢。我們看下偽代碼:

reduce()
{
    select total_amount from table_1
    if(total_amount < amount ){
          return failed.  
    }  
    //其他業(yè)務(wù)邏輯
    update total_amount = total_amount - amount where total_amount > amount; }

image.gif
  • 數(shù)據(jù)庫(kù)的版本號(hào)屬于樂觀鎖;

  • 通過CAS算法實(shí)現(xiàn)的類屬于樂觀鎖损谦。

悲觀鎖

悲觀鎖是怎么理解呢岖免?相對(duì)樂觀鎖剛好反過來,總是假設(shè)最壞的情況照捡,假設(shè)你每次拿數(shù)據(jù)的時(shí)候會(huì)被其他人修改颅湘,所以你在每次共享數(shù)據(jù)的時(shí)候會(huì)對(duì)他加一把鎖,等你使用完了再釋放鎖栗精,再給別人使用數(shù)據(jù)闯参。

悲觀鎖判斷在前,使用在后悲立。我們也看下偽代碼:

reduce()
{
    //其他業(yè)務(wù)邏輯
    int num = update total_amount = total_amount - amount where total_amount > amount; 
   if(num ==1 ){
          //業(yè)務(wù)邏輯.  
    } 
}

image.gif
  • Java中的的synchronize是重量級(jí)鎖 鹿寨,屬于悲觀鎖;

  • 數(shù)據(jù)庫(kù)行鎖屬于悲觀鎖薪夕;

扣減操作案例

這里舉一個(gè)非常常見的例子脚草,在高并發(fā)情況下余額扣減,或者類似商品庫(kù)存扣減原献,也可以是資金賬戶的余額扣減馏慨。扣減操作會(huì)發(fā)生什么問題呢姑隅?很容易可以看到写隶,可能會(huì)發(fā)生的問題是扣減導(dǎo)致的超賣,也就是扣減成了負(fù)數(shù)讲仰。

舉個(gè)例子慕趴,比如我的庫(kù)存數(shù)據(jù)只有100個(gè)。并發(fā)情況下第1筆請(qǐng)求賣出100個(gè),第2批賣出100元秩贰,導(dǎo)致當(dāng)前的庫(kù)存數(shù)量為負(fù)數(shù)霹俺。遇到這種場(chǎng)景應(yīng)該如何破解呢柔吼?這里列舉四種方案毒费。

方案1:同步排它鎖

這時(shí)候很容易想到最簡(jiǎn)單的方案:同步排它鎖(synchronize)。但是排他鎖的缺點(diǎn)很明顯:

  • 其中一個(gè)缺點(diǎn)是愈魏,線程串行導(dǎo)致的性能問題觅玻,性能消耗比較大。

  • 另一個(gè)缺點(diǎn)是無法解決分布式部署情況下跨進(jìn)程問題培漏;

方案2:數(shù)據(jù)庫(kù)行鎖

第二我們可能會(huì)想到溪厘,那用數(shù)據(jù)庫(kù)行鎖來鎖住這條數(shù)據(jù),這種方案相比排它鎖解決了跨進(jìn)程的問題牌柄,但是依然有缺點(diǎn)畸悬。

  • 其中一個(gè)缺點(diǎn)就是性能問題,在數(shù)據(jù)庫(kù)層面會(huì)一直阻塞珊佣,直到事務(wù)提交蹋宦,這里也是串行執(zhí)行;

  • 第二個(gè)需要注意設(shè)置事務(wù)的隔離級(jí)別是Read Committed咒锻,否則并發(fā)情況下冷冗,另外的事務(wù)無法看到提交的數(shù)據(jù),依然會(huì)導(dǎo)致超賣問題惑艇;

  • 缺點(diǎn)三是容易打滿數(shù)據(jù)庫(kù)連接蒿辙,如果事務(wù)中有第三方接口交互(存在超時(shí)的可能性),會(huì)導(dǎo)致這個(gè)事務(wù)的連接一直阻塞滨巴,打滿數(shù)據(jù)庫(kù)連接思灌。

  • 最后一個(gè)缺點(diǎn),容易產(chǎn)生交叉死鎖恭取,如果多個(gè)業(yè)務(wù)的加鎖控制不好泰偿,就會(huì)發(fā)生AB兩條記錄的交叉死鎖。

方案3:redis分布式鎖

前面的方案本質(zhì)上是把數(shù)據(jù)庫(kù)當(dāng)作分布式鎖來使用秽荤,所以同樣的道理甜奄,redis,zookeeper都相當(dāng)于數(shù)據(jù)庫(kù)的一種鎖窃款,其實(shí)當(dāng)遇到加鎖問題课兄,代碼本身無論是synchronize或者各種lock使用起來都比較復(fù)雜,所以思路是把代碼處理一致性的問難題交給一個(gè)能夠幫助你處理一致性的問題的專業(yè)組件晨继,比如數(shù)據(jù)庫(kù)烟阐,比如redis,比如zookeeper等。

這里我們分析下分布式鎖的優(yōu)缺點(diǎn):

  • 優(yōu)點(diǎn):

  • 可以避免大量對(duì)數(shù)據(jù)庫(kù)排他鎖的征用蜒茄,提高系統(tǒng)的響應(yīng)能力唉擂;

  • 缺點(diǎn):

  • 設(shè)置鎖和設(shè)置超時(shí)時(shí)間的原子性;

  • 不設(shè)置超時(shí)時(shí)間的缺點(diǎn)檀葛;

  • 服務(wù)宕機(jī)或線程阻塞超時(shí)的情況玩祟;

  • 超時(shí)時(shí)間設(shè)置不合理的情況;

加鎖和過期設(shè)置的原子性

redis加鎖的命令setnx屿聋,設(shè)置鎖的過期時(shí)間是expire空扎,解鎖的命令是del,但是2.6.12之前的版本中润讥,加鎖和設(shè)置鎖過期命令是兩個(gè)操作转锈,不具備原子性。如果setnx設(shè)置完key-value之后楚殿,還沒有來得及使用expire來設(shè)置過期時(shí)間撮慨,當(dāng)前線程掛掉了或者線程阻塞,會(huì)導(dǎo)致當(dāng)前線程設(shè)置的key一直有效脆粥,后續(xù)的線程無法正常使用setnx獲取鎖砌溺,導(dǎo)致死鎖。

針對(duì)這個(gè)問題冠绢,redis2.6.12以上的版本增加了可選的參數(shù)抚吠,可以在加鎖的同時(shí)設(shè)置key的過期時(shí)間,保證了加鎖和過期操作原子性的弟胀。

但是楷力,即使解決了原子性的問題,業(yè)務(wù)上同樣會(huì)遇到一些極端的問題孵户,比如分布式環(huán)境下萧朝,A獲取到了鎖之后,因?yàn)榫€程A的業(yè)務(wù)代碼耗時(shí)過長(zhǎng)夏哭,導(dǎo)致鎖的超時(shí)時(shí)間检柬,鎖自動(dòng)失效。后續(xù)線程B就意外的持有了鎖竖配,之后線程A再次恢復(fù)執(zhí)行何址,直接用del命令釋放鎖,這樣就錯(cuò)誤的將線程B同樣Key的鎖誤刪除了进胯。代碼耗時(shí)過長(zhǎng)還是比較常見的場(chǎng)景用爪,假如你的代碼中有外部通訊接口調(diào)用,就容易產(chǎn)生這樣的場(chǎng)景胁镐。

設(shè)置合理的時(shí)長(zhǎng)

剛才講到的線程超時(shí)阻塞的情況偎血,那么如果不設(shè)置時(shí)長(zhǎng)呢诸衔,當(dāng)然也不行,如果線程持有鎖的過程中突然服務(wù)宕機(jī)了颇玷,這樣鎖就永遠(yuǎn)無法失效了笨农。同樣的也存在鎖超時(shí)時(shí)間設(shè)置是否合理的問題,如果設(shè)置所持有時(shí)間過長(zhǎng)會(huì)影響性能帖渠,如果設(shè)置時(shí)間過短谒亦,有可能業(yè)務(wù)阻塞沒有處理完成,是否可以合理的設(shè)置鎖的時(shí)間?

續(xù)命鎖

這是一個(gè)很不容易解決的問題阿弃,不過有一個(gè)辦法能解決這個(gè)問題诊霹,那就是續(xù)命鎖羞延,我們可以先給鎖設(shè)置一個(gè)超時(shí)時(shí)間渣淳,然后啟動(dòng)一個(gè)守護(hù)線程,讓守護(hù)線程在一段時(shí)間之后重新去設(shè)置這個(gè)鎖的超時(shí)時(shí)間伴箩,續(xù)命鎖的實(shí)現(xiàn)過程就是寫一個(gè)守護(hù)線程入愧,然后去判斷對(duì)象鎖的情況,快失效的時(shí)候嗤谚,再次進(jìn)行重新加鎖棺蛛,但是一定要判斷鎖的對(duì)象是同一個(gè),不能亂續(xù)巩步。

同樣旁赊,主線程業(yè)務(wù)執(zhí)行完了,守護(hù)線程也需要銷毀椅野,避免資源浪費(fèi)终畅,使用續(xù)命鎖的方案相對(duì)比較而言更復(fù)雜,所以如果業(yè)務(wù)比較簡(jiǎn)單竟闪,可以根據(jù)經(jīng)驗(yàn)類比离福,合理的設(shè)置鎖的超時(shí)時(shí)間就行。

方案4:數(shù)據(jù)庫(kù)樂觀鎖

數(shù)據(jù)庫(kù)樂觀鎖加鎖的一個(gè)原則就是盡量想辦法減少鎖的范圍炼蛤。鎖的范圍越大妖爷,性能越差,數(shù)據(jù)庫(kù)的鎖就是把鎖的范圍減小到了最小理朋。我們看下面的偽代碼

reduce()
{
    select total_amount from table_1
    if(total_amount < amount ){
          return failed.  
    }  
    //其他業(yè)務(wù)邏輯
    update total_amount = total_amount - amount;  
}

image.gif

我們可以看到修改前的代碼是沒有where條件的絮识。修改后,再加where條件判斷:總庫(kù)存大于將被扣減的庫(kù)存嗽上。

update total_amount = total_amount - amount where total_amount > amount

image.gif

如果更新條數(shù)返回0次舌,說明在執(zhí)行過程中被其他線程搶先執(zhí)行扣減,并且避免了扣減為負(fù)數(shù)炸裆。

但是這種方案還會(huì)涉及一個(gè)問題垃它,如果在之前的update代碼中,以及其他的業(yè)務(wù)邏輯中還有一些其他的數(shù)據(jù)庫(kù)寫操作的話,那這部分?jǐn)?shù)據(jù)如何回滾呢国拇?

我的建議是這樣的洛史,你可以選擇下面這兩種寫法:

  • 利用事務(wù)回滾寫法:

我們先給業(yè)務(wù)方法增加事務(wù),方法在扣減庫(kù)存影響條數(shù)為零的時(shí)候扔出一個(gè)異常酱吝,這樣對(duì)他之前的業(yè)務(wù)代碼也會(huì)回滾也殖。

reduce()
{
    select total_amount from table_1
    if(total_amount < amount ){
          return failed.  
    }  
    //其他業(yè)務(wù)邏輯
    int num = update total_amount = total_amount - amount where total_amount > amount;   if(num==0) throw Exception;}

image.gif
  • 第二種寫法

    reduce() { //其他業(yè)務(wù)邏輯 int num = update total_amount = total_amount - amount where total_amount > amount; if(num ==1 ){ //業(yè)務(wù)邏輯.
    } else{ throw Exception; } }

首先執(zhí)行update業(yè)務(wù)邏輯,如果執(zhí)行成功了再去執(zhí)行邏輯操作务热,這種方案是我相對(duì)比較建議的方案忆嗜。在并發(fā)情況下對(duì)共享資源扣減操作可以使用這種方法,但是這里需要引出一個(gè)問題崎岂,比如說萬一其他業(yè)務(wù)邏輯中的業(yè)務(wù)捆毫,因?yàn)樘厥庠蚴×嗽撛趺崔k呢?比如說在扣減過程中服務(wù)OOM了怎么辦冲甘?

我只能說這些非常極端的情況绩卤,比如突然宕機(jī)中間數(shù)據(jù)都丟了,這種極少數(shù)的情況下只能人工介入江醇,如果所有的極端情況都考慮到濒憋,也不現(xiàn)實(shí)。我們討論的重點(diǎn)是并發(fā)情況下陶夜,共享資源的操作如何加鎖的問題凛驮。

總結(jié)

最后我來給你總結(jié)一下,如果你可以非常熟練的解決這類問題条辟,第一時(shí)間肯定想到的是:數(shù)據(jù)庫(kù)版本號(hào)解決方案或者分布式鎖的解決方案黔夭;但是如果你是一個(gè)初學(xué)者,相信你一定會(huì)第一時(shí)間考慮到Java中提供的同步鎖或者數(shù)據(jù)庫(kù)行鎖捂贿。
小編總結(jié)了2020面試題纠修,這份面試題的包含的模塊分為19個(gè)模塊,分別是: Java 基礎(chǔ)厂僧、容器扣草、多線程、反射颜屠、對(duì)象拷貝辰妙、Java Web 、異常甫窟、網(wǎng)絡(luò)密浑、設(shè)計(jì)模式、Spring/Spring MVC粗井、Spring Boot/Spring Cloud尔破、Hibernate街图、MyBatis、RabbitMQ懒构、Kafka餐济、Zookeeper、MySQL胆剧、Redis絮姆、JVM 。
關(guān)注公眾號(hào):程序員白楠楠秩霍,獲取上述資料篙悯。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市铃绒,隨后出現(xiàn)的幾起案子鸽照,更是在濱河造成了極大的恐慌,老刑警劉巖匿垄,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件移宅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡椿疗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門糠悼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來届榄,“玉大人,你說我怎么就攤上這事倔喂÷撂酰” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵席噩,是天一觀的道長(zhǎng)班缰。 經(jīng)常有香客問我,道長(zhǎng)悼枢,這世上最難降的妖魔是什么埠忘? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮馒索,結(jié)果婚禮上莹妒,老公的妹妹穿的比我還像新娘。我一直安慰自己绰上,他們只是感情好旨怠,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蜈块,像睡著了一般鉴腻。 火紅的嫁衣襯著肌膚如雪迷扇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天爽哎,我揣著相機(jī)與錄音谋梭,去河邊找鬼。 笑死倦青,一個(gè)胖子當(dāng)著我的面吹牛瓮床,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播产镐,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼隘庄,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了癣亚?” 一聲冷哼從身側(cè)響起丑掺,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎述雾,沒想到半個(gè)月后街州,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡玻孟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年唆缴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片黍翎。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡面徽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匣掸,到底是詐尸還是另有隱情趟紊,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布碰酝,位于F島的核電站霎匈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏送爸。R本人自食惡果不足惜铛嘱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碱璃。 院中可真熱鬧弄痹,春花似錦、人聲如沸嵌器。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爽航。三九已至蚓让,卻和暖如春乾忱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背历极。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工窄瘟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人趟卸。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓蹄葱,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親锄列。 傳聞我的和親對(duì)象是個(gè)殘疾皇子图云,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容