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