摘要:并發(fā)同步控制遇到并發(fā)時(shí),我們避免不了要談并發(fā)控制。它會(huì)阻塞其它的線(xiàn)程執(zhí)行,如果當(dāng)前線(xiàn)程一直持有的監(jiān)控鎖,就會(huì)把其它線(xiàn)程一直阻塞下去。如果此時(shí)線(xiàn)程和線(xiàn)程同時(shí)進(jìn)入方法,用一段語(yǔ)言描述方法的執(zhí)行過(guò)程,可能是這樣子。
并發(fā)同步控制
遇到并發(fā)時(shí),我們避免不了要談并發(fā)控制。在Java語(yǔ)言中,我們談并發(fā)時(shí),要談到Object的監(jiān)控鎖。在MySQL的數(shù)據(jù)庫(kù)并發(fā)中,我們也要談到mysql的鎖機(jī)制。
這樣說(shuō),說(shuō)到并發(fā)就避免不了鎖的概念,不管是在像Java這種語(yǔ)言還是MySQL這樣的數(shù)據(jù)庫(kù)產(chǎn)品,我們都是利用鎖進(jìn)行并發(fā)控制,或者說(shuō)是同步控制。
控制并發(fā)的兩種方式(樂(lè)觀(guān)鎖與悲觀(guān)鎖)我們一般都可以想到或者可以理解的簡(jiǎn)單解決辦法就是,把并發(fā)轉(zhuǎn)成串行的執(zhí)行的辦法。在簡(jiǎn)單的串行情況,不存在并發(fā)的問(wèn)題,那我們自然也就不存在鎖的概念了。拿Java的線(xiàn)程同步來(lái)說(shuō),如果有一個(gè)變量 a = 1 此時(shí)如果有兩個(gè)線(xiàn)程修改同執(zhí)行下面操作
a = 2; a = 0;
那么我們一般可以通過(guò)下面形式進(jìn)行解決
public final static Object writeMonitor = new Object() void setA(int a){ synchronize(writeMonitor){ this.a = a; } }
此時(shí),兩個(gè)線(xiàn)程只有一個(gè)線(xiàn)程執(zhí)行完上面步驟后,才會(huì)允許下一個(gè)線(xiàn)程執(zhí)行。這就是把并發(fā)轉(zhuǎn)為串行的列子。它會(huì)阻塞其它的線(xiàn)程執(zhí)行,如果當(dāng)前線(xiàn)程一直持有的writeMonitor監(jiān)控鎖,就會(huì)把其它線(xiàn)程一直阻塞下去。這種并發(fā)控制的鎖,我們一般稱(chēng)為悲觀(guān)鎖。對(duì)應(yīng)的MySQL的Innodb引擎來(lái)說(shuō),我們利用Innodb的行鎖就是悲觀(guān)鎖的一種方式,但實(shí)際生產(chǎn)環(huán)境中,我們會(huì)很少使用它的行鎖,即很少用悲觀(guān)鎖去解決數(shù)據(jù)庫(kù)的并發(fā)問(wèn)題。
在悲觀(guān)鎖的這種控制情況下,我們可以理解為:?jiǎn)栴}總是很糟糕,只能以最粗暴也最簡(jiǎn)單的解決方式,就是所有的并發(fā)都給我一個(gè)一個(gè)執(zhí)行。這種方式在某些場(chǎng)景確實(shí)很有用,比如redis的并發(fā)控制就是這么實(shí)現(xiàn)。
相對(duì)于悲觀(guān)鎖這種方式,還會(huì)有另外一種解決并發(fā)的辦法。還以Java語(yǔ)言中的一些設(shè)計(jì)來(lái)談。在Java的并發(fā)工具包JUC下有個(gè)atomic包,比如AtomicIntegr,我們知道這些封裝好的類(lèi)都是線(xiàn)程安全的工具類(lèi),可以直接在多線(xiàn)程環(huán)境下使用,說(shuō)下getAndSet方法
public final int getAndSet(int newValue){ for( ;; ){ int current = get(); if(compareAndSet(current,newValue)){ return current; } } }
在這個(gè)方法中,我們并沒(méi)有看到synchronize關(guān)鍵字,代碼也很簡(jiǎn)潔。關(guān)鍵的地方在與compareAndSet(current,newValue)這個(gè)方法的設(shè)計(jì)。在Java中它是一個(gè)本地方法,所以我們看不到它的具體實(shí)現(xiàn)。可以去google下查看具體的設(shè)計(jì),這里我說(shuō)下我的認(rèn)識(shí)。如果有個(gè)變量 a = 1 此時(shí)有兩個(gè)線(xiàn)程同時(shí)執(zhí)行下面操作:
a = 1; a = 2;
為了下面可以方便簡(jiǎn)單的描述問(wèn)題,我們認(rèn)為對(duì)變量的更改是原子性的,即不談Java的內(nèi)存模型問(wèn)題或忽視線(xiàn)程可見(jiàn)問(wèn)題。如果此時(shí)線(xiàn)程A和線(xiàn)程B同時(shí)進(jìn)入getAndSet方法,用一段語(yǔ)言描述compareAndSet方法的執(zhí)行過(guò)程,可能是這樣子。
A線(xiàn)程讀取到current = 0; B線(xiàn)程讀取到current = 0; A執(zhí)行set方法,先去把自己current值和內(nèi)存中現(xiàn)有的值(我們把該值成為memory)比較,發(fā)現(xiàn) current = memory = 0, A線(xiàn)程更改 a 成功,此時(shí)memory = 1 這時(shí)B線(xiàn)程執(zhí)行set方法,則會(huì)有這樣的 current != memory ,B線(xiàn)程更新失敗。 于是B線(xiàn)程重新進(jìn)行,此時(shí)獲取current = 1,在執(zhí)行set方法,current = memory = 1 ,OK B線(xiàn)程也執(zhí)行成功。 最終 memory = 2;
這種并發(fā)控制和上述悲觀(guān)鎖的并發(fā)控制方式,主要區(qū)別就是,沒(méi)有阻塞。它不會(huì)阻塞其它并發(fā)的操作行為,而是讓他們嘗試更新。這種嘗試的去更新的控制形式,我們叫它樂(lè)觀(guān)鎖。樂(lè)觀(guān)的說(shuō)法就是體現(xiàn)在不會(huì)阻塞其它并發(fā)者。這種樂(lè)觀(guān)鎖在實(shí)際電商業(yè)務(wù)中則很常見(jiàn),比如更新庫(kù)存,比如hibernate的樂(lè)觀(guān)鎖實(shí)現(xiàn)。
數(shù)據(jù)庫(kù)的悲觀(guān)鎖和樂(lè)觀(guān)鎖并發(fā)控制在數(shù)據(jù)庫(kù)中,以mysql的Innodb引擎為例,下面語(yǔ)句就是悲觀(guān)鎖的使用方式
start transaction; select * from message where id= 1 for update; update set … where id = 1; commit;
上述for update會(huì)鎖住id =1的這行數(shù)據(jù),它會(huì)阻塞其它連接查詢(xún)改行數(shù)據(jù)。在實(shí)際生成環(huán)境中卻很少使用。以電影院的售票系統(tǒng)來(lái)講,在用戶(hù)并發(fā)購(gòu)買(mǎi)座位時(shí),肯定會(huì)存在并發(fā)購(gòu)買(mǎi)的問(wèn)題。這時(shí)一般我們都會(huì)通過(guò)增加一個(gè)version字段來(lái)解決問(wèn)題
start transaction; // ticket 代表電影票下的座位分布信息,status = 1 代表座位已經(jīng)被預(yù)定。 update ticket set `status` = 1 ,version = 1 where id =1 and version =0; commit;
這時(shí)version字段就相當(dāng)于并發(fā)訪(fǎng)問(wèn)下的版本控制如果有人預(yù)定version的字段就變?yōu)?,如果發(fā)現(xiàn)設(shè)置status字段為1時(shí)version不是1就更新失敗,這就是通過(guò)樂(lè)觀(guān)鎖的方式進(jìn)行并發(fā)控制的一種方式。上述語(yǔ)句其實(shí)也可以不增加version字段,這里主要方便敘述問(wèn)題,直接寫(xiě)成下面這樣也OK
start transaction; update ticket set `status` = 1 where status = 0; commit;
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/65861.html
摘要:比如主協(xié)程啟動(dòng)個(gè)子協(xié)程,主協(xié)程等待所有子協(xié)程退出后再繼續(xù)后續(xù)流程,這種場(chǎng)景下也可輕易實(shí)現(xiàn)。這個(gè)例子中,父協(xié)程僅僅是等待子協(xié)程結(jié)束,其實(shí)父協(xié)程也可以向管道中寫(xiě)入數(shù)據(jù)通知子協(xié)程結(jié)束,這時(shí)子協(xié)程需要定期地探測(cè)管道中是否有消息出現(xiàn)。一.設(shè)計(jì)原理Go 語(yǔ)言中最常見(jiàn)的、也是經(jīng)常被人提及的設(shè)計(jì)模式就是:不要通過(guò)共享內(nèi)存來(lái)通信,我們應(yīng)該使用通信來(lái)共享內(nèi)存通過(guò)共享內(nèi)存來(lái)通信是直接讀取內(nèi)存的數(shù)據(jù),而通過(guò)通信來(lái)共...
摘要:故事開(kāi)始了,小程序圖片合成真機(jī)測(cè)試時(shí),會(huì)報(bào)錯(cuò)。所以只能將異步并發(fā)改為同步阻塞式渲染。 showImg(https://segmentfault.com/img/remote/1460000013228074); 故事開(kāi)始了,小程序canvas圖片合成 真機(jī)測(cè)試時(shí),會(huì)報(bào)錯(cuò):getImageInfo failed 。也就是說(shuō),我這邊異步請(qǐng)求50張圖片,每張圖片都是通過(guò)getImageInf...
摘要:而是在調(diào)用發(fā)出后,被調(diào)用者通過(guò)狀態(tài)通知來(lái)通知調(diào)用者,或通過(guò)回調(diào)函數(shù)處理這個(gè)調(diào)用。請(qǐng)求程序發(fā)出請(qǐng)求,從服務(wù)器端獲取數(shù)據(jù),并設(shè)置了回調(diào)函數(shù)。然后,瀏覽器會(huì)設(shè)置偵聽(tīng)來(lái)自網(wǎng)絡(luò)的響應(yīng),拿到數(shù)據(jù)后,將該回調(diào)函數(shù)插入到事件循環(huán)。 并發(fā)與并行 并發(fā)是指兩個(gè)或多個(gè)事件鏈隨時(shí)間發(fā)展交替執(zhí)行,以至于從更高的層次來(lái)看,就像是同時(shí)運(yùn)行(但在任意時(shí)刻只處理一個(gè)事件) 并發(fā)的關(guān)鍵是你有處理多個(gè)任務(wù)的能力,不一定同...
摘要:在接下來(lái)的分鐘,你將會(huì)學(xué)會(huì)如何通過(guò)同步關(guān)鍵字,鎖和信號(hào)量來(lái)同步訪(fǎng)問(wèn)共享可變變量。所以在使用樂(lè)觀(guān)鎖時(shí),你需要每次在訪(fǎng)問(wèn)任何共享可變變量之后都要檢查鎖,來(lái)確保讀鎖仍然有效。 原文:Java 8 Concurrency Tutorial: Synchronization and Locks譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 歡迎閱讀我的Java8并發(fā)教程的第二部分。這份指南將...
閱讀 3845·2021-11-24 09:39
閱讀 3766·2021-11-22 12:07
閱讀 1116·2021-11-04 16:10
閱讀 809·2021-09-07 09:59
閱讀 1908·2019-08-30 15:55
閱讀 947·2019-08-30 15:54
閱讀 734·2019-08-29 14:06
閱讀 2484·2019-08-27 10:54