Java架構筆記——分佈式鎖

Java架構筆記——分佈式鎖

分佈式鎖

併發編程中的鎖併發編程的鎖機制:synchronized和lock。在單進程的系統中,當存在多個線程可以同時改變某個變量時,就需要對變量或代碼塊做同步,使其在修改這種變量時能夠線性執行消除併發修改變量。

而同步的本質是通過鎖來實現的。為了實現多個線程在一個時刻同一個代碼塊只能有一個線程可執行,那麼需要在某個地方做個標記,這個標記必須每個線程都能看到,當標記不存在時可以設置該標記,其餘後續線程發現已經有標記了則等待擁有標記的線程結束同步代碼塊取消標記後再去嘗試設置標記。

分佈式環境下,數據一致性問題一直是一個比較重要的話題,而又不同於單進程的情況。分佈式與單機情況下最大的不同在於其不是多線程而是多進程。多線程由於可以共享堆內存,因此可以簡單的採取內存作為標記存儲位置。而進程之間甚至可能都不在同一臺物理機上, 因此需要將標記存儲在一個所有進程都能看到的地方。 常見的是秒殺場景,訂單服務部署了多個實例

,如

秒殺商品有4個,第一個用戶購買3個,第二個用戶購買2個,理想狀態下第一個用戶能購買成功,第二

個用戶提示購買失敗,反之亦然。而實際可能出現的情況是,兩個用戶都得到庫存為4,第一個用戶

買到了3個,更新庫存之前,第二個用戶下了2個商品的訂單,更新庫存為2,導致出錯。

在上面的場景中,商品的庫存是共享變量,面對高併發情形,需要保證對資源的訪問互斥。在單機環境中,java中其實提供了很多併發處理相關的API,但是這些API在分佈式場景中就無能為力了。也就是說單純的java API 並不能提供分佈式鎖的能力。分佈式系統中,由於分佈式系統的分佈性,即多線程和多進程並且分佈在不同機器中,synchronized和lock這兩種鎖將失去原有鎖的效果,需要我們自已實現分佈式鎖。

常見的分佈式鎖如下:

  • 基於數據庫實現分佈式鎖:有性能問題
  • 基於緩存實現分佈式鎖,如redis
  • 基於zookeeper實現分佈式鎖

使用setnx實現分佈式鎖

setnx key value

setnx是將key的值設為value,當且僅當key不存在。若給定的key已經存在,則setnx不做任何動作。

返回1,說明該進程獲得鎖,setnx將鍵(lock.id)的值設置為鎖的超時時間,當前時間+加上鎖的有效時間。

返回0,說明其他進程已經獲得了鎖,進程不能進入臨界區。進程可以在一個循環中不斷地嘗試setnx操作,以獲得鎖。

Java架構筆記——分佈式鎖

存在死鎖的問題

在線程釋放鎖,即執行del lock.id操作前,需要先判斷鎖是否已超時。如果鎖已超時,那麼鎖可能已由其他線程獲得,這時直接執行del lock.id操作會導致把其他線程已獲得的鎖釋放掉。

獲取分佈式鎖

public boolean lock( long timeout, TimeUnit timeUnit ) throws InterruptedException
{
timeout = timeUnit.toMillis( timeout );
long time = timeout + System.currentTimeMillis();
lock.tryLock( timeout, timeUnit );
try{
while ( true )
{
boolean hasLock = tryLock();
if ( hasLock )
{
return(true); /* 獲得鎖 */
}else if ( timeout < System.currentTimeMillis() )
{
break;
}
Thread.sleep( 1000 );
}
} finally {
if ( lock.isHeldByCurrentThread() )
{
lock.unlock();
}
}
return(false);
}
public boolean tryLock()
{
long time = System.currentTimeMillis();
long timeout = 2000;

String expires = String.valueOf( timeout + time );
if ( redisService.setnx( "lock.id", expires ) > 0 )
{
/* 獲取鎖,設置超時時間 */
setLockStatus( expires );
return(true);
}else{
String locktime = redisService.get( "lock.id" );
/* 檢查鎖是否超時 */
if ( locktime != null && Long.parseLong( locktime ) < time )
{
String oldlocktime = redis.getset( "lock.id", expires );
/* 舊值與當前時間比較 */
if ( oldlocktime != null && locktime.equals( oldlocktime ) )
{
/* 獲取鎖,設置超時時間 */
setLockStatus( expires );
return(true);
}
}
return(false);
}
}

釋放鎖

public boolean unlock()
{
if ( lockHolder == Thread.currentThread() )
{
/* 判斷鎖是否超時,沒有超時才將互斥量刪除 */
if ( lockExpiresTime > System.currentTimeMillis() )
{
redisService.del( "lock.id" );
}
lockHolder = null;
return(true);
}else{
throws new IllegalMonitorStateException( "無法執行解鎖操作" );
}
}

Java架構筆記——分佈式鎖

如果你是Java程序員,對技術提升很感興趣,可以關注我私信“架構”免費獲取筆者整理的適合1~5年的Java工程師學習參考的資源。還有大量面試題以及解析。歡迎各位工程師加入,合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!


Java架構筆記——分佈式鎖


Java架構筆記——分佈式鎖



分享到:


相關文章: