欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java源碼解析重寫鎖的設(shè)計結(jié)構(gòu)和細節(jié)

 更新時間:2022年03月11日 14:38:39   作者:Q.E.D.  
這篇文章主要為大家介紹了Java源碼解析重寫鎖的設(shè)計結(jié)構(gòu)和細節(jié),這小節(jié)我們以共享鎖作為案列,自定義一個共享鎖。有需要的朋友可以借鑒參考下

引導語

有的面試官喜歡讓同學在說完鎖的原理之后,讓你重寫一個新的鎖,要求現(xiàn)場在白板上寫出大概的思路和代碼邏輯,這種面試題目,蠻難的,我個人覺得其側(cè)重點主要是兩個部分:

考察一下你對鎖原理的理解是如何來的,如果你對源碼沒有解讀過的話,只是看看網(wǎng)上的文章,或者背面試題,也是能夠說出大概的原理,但你很難現(xiàn)場寫出一個鎖的實現(xiàn)代碼,除非你真的看過源碼,或者有和鎖相關(guān)的項目經(jīng)驗;

我們不需要創(chuàng)造,我們只需要模仿 Java 鎖中現(xiàn)有的 API 進行重寫即可。

如果你看過源碼,這道題真的很簡單,你可以挑選一個你熟悉的鎖進行模仿。

在鎖章節(jié)中我們之前說的都是排它鎖,這小節(jié)我們以共享鎖作為案列,自定義一個共享鎖。

1、需求

一般自定義鎖的時候,我們都是根據(jù)需求來進行定義的,不可能憑空定義出鎖來,說到共享鎖,大家可能會想到很多場景,比如說對于共享資源的讀鎖可以是共享的,比如對于數(shù)據(jù)庫鏈接的共享訪問,比如對于 Socket 服務端的鏈接數(shù)是可以共享的,場景有很多,我們選擇共享訪問數(shù)據(jù)庫鏈接這個場景來定義一個鎖。

2、詳細設(shè)計

假定(以下設(shè)想都為假定)我們的數(shù)據(jù)庫是單機 mysql,只能承受 10 個鏈接,創(chuàng)建數(shù)據(jù)庫鏈接時,我們是通過最原始 JDBC 的方式,我們用一個接口把用 JDBC 創(chuàng)建鏈接的過程進行了封裝,這個接口我們命名為:創(chuàng)建鏈接接口。

共享訪問數(shù)據(jù)庫鏈接的整體要求如下:所有請求加在一起的 mysql 鏈接數(shù),最大不能超過 10(包含 10),一旦超過 10,直接報錯。

在這個背景下,我們進行了下圖的設(shè)計:

圖片描述

這個設(shè)計最最關(guān)鍵的地方,就是我們通過能否獲得鎖,來決定是否可以得到 mysql 鏈接,如果能獲得鎖,那么就能得到鏈接,否則直接報錯。

接著我們一起來看下落地的代碼:

2.1、定義鎖 

首先我們需要定義一個鎖出來,定義時需要有兩個元素:

鎖的定義:同步器 Sync;鎖對外提供的加鎖和解鎖的方法。

共享鎖的代碼實現(xiàn)如下:

// 共享不公平鎖
public class ShareLock implements Serializable{
	// 同步器
  private final Sync sync;
  // 用于確保不能超過最大值
  private final int maxCount;
  /**
   * 初始化時給同步器 sync 賦值
   * count 代表可以獲得共享鎖的最大值
   */
  public ShareLock(int count) {
    this.sync = new Sync(count);
    maxCount = count;
  }
  /**
   * 獲得鎖
   * @return true 表示成功獲得鎖,false 表示失敗
   */
  public boolean lock(){
    return sync.acquireByShared(1);
  }
  /**
   * 釋放鎖
   * @return true 表示成功釋放鎖,false 表示失敗
   */
  public boolean unLock(){
    return sync.releaseShared(1);
  }
}  

從上述代碼中可以看出,加鎖和釋放鎖的實現(xiàn),都依靠同步器 Sync 的底層實現(xiàn)。

唯一需要注意的是,鎖需要規(guī)定好 API 的規(guī)范,主要是兩方面:

API 需要什么,就是鎖在初始化的時候,你需要傳哪些參數(shù)給我,在 ShareLock 初始化時,需要傳最大可共享鎖的數(shù)目;

需要定義自身的能力,即定義每個方法的入?yún)⒑统鰠?。?ShareLock 的實現(xiàn)中,加鎖和釋放鎖的入?yún)⒍紱]有,是方法里面寫死的 1,表示每次方法執(zhí)行,只能加鎖一次或釋放鎖一次,出參是布爾值,true 表示加鎖或釋放鎖成功,false 表示失敗,底層使用的都是 Sync 非公平鎖。

以上這種思考方式是有方法論的,就是我們在思考一個問題時,可以從兩個方面出發(fā):API 是什么?API 有什么能力?

2.2、定義同步器 Sync

Sync 直接繼承 AQS ,代碼如下:

class Sync extends AbstractQueuedSynchronizer {
   // 表示最多有 count 個共享鎖可以獲得
  public Sync(int count) {
    setState(count);
  }
  // 獲得 i 個鎖
  public boolean acquireByShared(int i) {
    // 自旋保證 CAS 一定可以成功
    for(;;){
      if(i<=0){
        return false;
      }
      int state = getState();
      // 如果沒有鎖可以獲得,直接返回 false
      if(state <=0 ){
        return false;
      }
      int expectState = state - i;
      // 如果要得到的鎖不夠了,直接返回 false
      if(expectState < 0 ){
        return false;
      }
      // CAS 嘗試得到鎖,CAS 成功獲得鎖,失敗繼續(xù) for 循環(huán)
      if(compareAndSetState(state,expectState)){
        return true;
      }
    }
  }
  // 釋放 i 個鎖
  @Override
  protected boolean tryReleaseShared(int arg) {
    for(;;){
      if(arg<=0){
        return false;
      }
      int state = getState();
      int expectState = state + arg;
      // 超過了 int 的最大值,或者 expectState 超過了我們的最大預期
      if(expectState < 0 || expectState > maxCount){
        log.error("state 超過預期,當前 state is {},計算出的 state is {}",state
        ,expectState);
        return false;
      }
      if(compareAndSetState(state, expectState)){
        return true;
      }
    }
  }
}

整個代碼比較清晰,我們需要注意的是:

邊界的判斷,比如入?yún)⑹欠穹欠?,釋放鎖時,會不會出現(xiàn)預期的 state 非法等邊界問題,對于此類問題我們都需要加以判斷,體現(xiàn)出思維的嚴謹性;

加鎖和釋放鎖,需要用 for 自旋 + CAS 的形式,來保證當并發(fā)加鎖或釋放鎖時,可以重試成功。寫 for 自旋時,我們需要注意在適當?shù)臅r機要 return,不要造成死循環(huán),CAS 的方法 AQS 已經(jīng)提供了,不要自己寫,我們自己寫的 CAS 方法是無法保證原子性的。

2.3、通過能否獲得鎖來決定能否得到鏈接

鎖定義好了,我們需要把鎖和獲取 Mysql 鏈接結(jié)合起來,我們寫了一個 Mysql 鏈接的工具類,叫做 MysqlConnection,其主要負責兩大功能:

通過 JDBC 建立和 Mysql 的鏈接;

結(jié)合鎖,來防止請求過大時,Mysql 的總鏈接數(shù)不能超過 10 個。

首先我們看下 MysqlConnection 初始化的代碼:

public class MysqlConnection {
  private final ShareLock lock;
  // maxConnectionSize 表示最大鏈接數(shù)
  public MysqlConnection(int maxConnectionSize) {
    lock = new ShareLock(maxConnectionSize);
  }
}

我們可以看到,在初始化時,需要制定最大的鏈接數(shù)是多少,然后把這個數(shù)值傳遞給鎖,因為最大的鏈接數(shù)就是 ShareLock 鎖的 state 值。

接著為了完成 1,我們寫了一個 private 的方法:

// 得到一個 mysql 鏈接,底層實現(xiàn)省略
private Connection getConnection(){}

然后我們實現(xiàn) 2,代碼如下:

// 對外獲取 mysql 鏈接的接口
// 這里不用try finally 的結(jié)構(gòu),獲得鎖實現(xiàn)底層不會有異常
// 即使出現(xiàn)未知異常,也無需釋放鎖
public Connection getLimitConnection() {
  if (lock.lock()) {
    return getConnection();
  }
  return null;
}
// 對外釋放 mysql 鏈接的接口
public boolean releaseLimitConnection() {
  return lock.unLock();
}

邏輯也比較簡單,加鎖時,如果獲得了鎖,就能返回 Mysql 的鏈接,釋放鎖時,在鏈接關(guān)閉成功之后,調(diào)用 releaseLimitConnection 方法即可,此方法會把鎖的 state 狀態(tài)加一,表示鏈接被釋放了。

以上步驟,針對 Mysql 鏈接限制的場景鎖就完成了。

3、測試

鎖寫好了,接著我們來測試一下,我們寫了一個測試的 demo,代碼如下:

public static void main(String[] args) {
  log.info("模仿開始獲得 mysql 鏈接");
  MysqlConnection mysqlConnection = new MysqlConnection(10);
  log.info("初始化 Mysql 鏈接最大只能獲取 10 個");
  for(int i =0 ;i<12;i++){
    if(null != mysqlConnection.getLimitConnection()){
      log.info("獲得第{}個數(shù)據(jù)庫鏈接成功",i+1);
    }else {
      log.info("獲得第{}個數(shù)據(jù)庫鏈接失?。簲?shù)據(jù)庫連接池已滿",i+1);
    }
  }
  log.info("模仿開始釋放 mysql 鏈接");
  for(int i =0 ;i<12;i++){
    if(mysqlConnection.releaseLimitConnection()){
      log.info("釋放第{}個數(shù)據(jù)庫鏈接成功",i+1);
    }else {
      log.info("釋放第{}個數(shù)據(jù)庫鏈接失敗",i+1);
    }
  }
  log.info("模仿結(jié)束");
}

以上代碼邏輯如下:

獲得 Mysql 鏈接邏輯:for 循環(huán)獲取鏈接,1~10 都可以獲得鏈接,11~12 獲取不到鏈接,因為鏈接被用完了;釋放鎖邏輯:for 循環(huán)釋放鏈接,1~10 都可以釋放成功,11~12 釋放失敗。

我們看下運行結(jié)果,如下圖:

圖片描述

從運行的結(jié)果,可以看出,我們實現(xiàn)的 ShareLock 鎖已經(jīng)完成了 Mysql 鏈接共享的場景了。

4、總結(jié) 

同學們閱讀到這里不知道有沒有兩點感受:

重寫鎖真的很簡單,最關(guān)鍵的是要和場景完美貼合,能滿足業(yè)務場景的鎖才是好鎖;

鎖其實只是來滿足業(yè)務場景的,本質(zhì)都是 AQS,所以只要 AQS 學會了,在了解清楚場景的情況下,重寫鎖都不難的。

鎖章節(jié)最核心的就是 AQS 源碼解析的兩章,只要我們把 AQS 弄懂了,其余鎖的實現(xiàn),只要稍微看下源碼實現(xiàn),幾乎馬上就能知道其底層實現(xiàn)的原理,大多數(shù)都是通過操作 state 來完成不同的場景需求,所以還是建議大家多看 AQS 源碼,多 debug AQS 源碼,只要 AQS 弄清楚了,鎖都很簡單。

以上就是Java源碼解析重寫鎖的設(shè)計結(jié)構(gòu)和細節(jié)的詳細內(nèi)容,更多關(guān)于Java重寫鎖設(shè)計結(jié)構(gòu)和細節(jié)解析的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論