併發同步控制

併發同步控制

遇到併發時,咱們避免不了要談併發控制。在Java語言中,咱們談併發時,要談到Object的監控鎖。在MySQL的數據庫併發中,咱們也要談到mysql的鎖機制。mysql

這樣說,說到併發就避免不了鎖的概念,無論是在像Java這種語言仍是MySQL這樣的數據庫產品,咱們都是利用鎖進行併發控制,或者說是同步控制。redis

控制併發的兩種方式(樂觀鎖與悲觀鎖)

咱們通常均可以想到或者能夠理解的簡單解決辦法就是,把併發轉成串行的執行的辦法。在簡單的串行狀況,不存在併發的問題,那咱們天然也就不存在鎖的概念了。拿Java的線程同步來講,若是有一個變量 a = 1 此時若是有兩個線程修改同執行下面操做sql

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方法的執行過程,多是這樣子。this

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的樂觀鎖實現。google

數據庫的悲觀鎖和樂觀鎖併發控制

在數據庫中,以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;
相關文章
相關標籤/搜索