ホームページ  >  記事  >  Java  >  Javaリライトロックの設計構造と詳細は何ですか

Javaリライトロックの設計構造と詳細は何ですか

王林
王林転載
2023-04-18 17:22:031186ブラウズ

    はじめに

    一部の面接官は、学生にロックの原理を説明した後に新しいロックを書き直すよう求め、現場に立ち会ってもらうことを好みます。一般的なアイデアとコード ロジックをホワイトボードに書きます。この種の面接の質問は非常に難しいです。個人的には、次の 2 つの部分に焦点が当てられていると思います:

    ロック原理をどのように理解したか調べてください。まだ理解していない場合は、ソースコードを解釈すれば、オンラインの記事を読んだり、インタビューの質問を覚えたりするだけで大​​まかな原理はわかりますが、実際にソースコードを読んでいない限り、その場でロックの実装コードを書くのは困難です。ロックに関連するプロジェクトの経験がある;

    作成する必要はなく、Java ロックの既存の API を模倣して書き直すだけで済みます。

    ソース コードを読んだことがあれば、この質問は非常に簡単です。使い慣れたロックを選択して模倣することができます。

    1. 要件

    一般に、ロックをカスタマイズするときは、要件に基づいてロックを定義します。何もないところからロックを定義することは不可能です。共有ロックに関しては、多くのものが思い浮かぶかもしれません。シナリオ、たとえば、共有リソースの読み取りロックを共有できる、データベース リンクへの共有アクセスなど、たとえば、ソケット サーバー上のリンクの数を共有できるなど、多くのシナリオがあります。データベース リンクにアクセスしてロックを定義します。

    2. 詳細な設計

    データベースはスタンドアロンの mysql であり、10 個の接続しかサポートできないと仮定します (以下の前提はすべて前提です)。データベース接続を作成するときは、次を使用します。オリジナルの JDBC このように、JDBC を使用してリンクを作成するプロセスをカプセル化するインターフェイスを使用します。このインターフェイスに Create Link Interface という名前を付けます。

    共有アクセス データベース リンクの全体的な要件は次のとおりです: すべてのリクエストを合わせた mysql リンクの数は 10 (両端を含む) を超えることはできません。10 を超えると、エラーが直接報告されます。

    これに関連して、以下に示すように設計を実行しました。

    Javaリライトロックの設計構造と詳細は何ですか

    この設計の最も重要な部分は、それを介してロックを取得できるかどうかです。 mysql リンクを取得できるかどうかを判断するには、ロックを取得できる場合はリンクを取得でき、そうでない場合はエラーが直接報告されます。

    次に、実装されたコードを見てみましょう:

    2.1. ロックの定義

    最初にロックを定義する必要があり、それには 2 つの要素が必要です:

    ロックの定義: シンクロナイザー Sync; ロックによって提供されるロックおよびロック解除の方法。

    共有ロックのコード実装は次のとおりです。

    // 共享不公平锁
    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);
      }
    }

    上記のコードからわかるように、ロックのロックと解放の実装は、基礎となるシンクロナイザー Sync の実装に依存しています。

    注意する必要がある唯一のことは、ロックには主に 2 つの側面で優れた API 仕様が必要であるということです。

    API で必要なのは、次のときにどのようなパラメーターを渡す必要があるかです。 ShareLock を初期化するときは、共有可能なロックの最大数を渡す必要があります。

    独自の機能を定義する必要があります。つまり、各メソッドの入力パラメータと出力パラメータを定義する必要があります。 ShareLock の実装には、ロックのロックと解放のための入力パラメータはありません。これらはメソッド内でハードコーディングされています 1。つまり、メソッドが実行されるたびに、ロックは 1 回のみロックまたは解放されます。出力パラメータはブール値で、true はロックまたはロックの解放が成功したことを意味し、false は失敗を示し、最下位層は不当な同期ロックを使用します。

    上記の考え方には方法論があります。つまり、問題を考えるとき、次の 2 つの側面から始めることができます。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 成功获得锁,失败继续 for 循环
          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;
          }
        }
      }
    }

    コード全体は比較的明確ですが、注意する必要がある点は次のとおりです。

    入力パラメータが不正かどうか、ロックを解除したときに予想される不正な状態が発生するかどうかなどの境界判断、思考の厳密さを反映して判断する必要がある;

    追加 ロックを同時にロックまたは解放するときに再試行が成功することを保証するには、ロックとロックの解放をスピン CAS の形式にする必要があります。スピン用に書く場合は、適切なタイミングでリターンすることと、無限ループにならないように注意する必要があります。CAS メソッドは AQS から提供されています。自分で書かないでください。自分で書いた CAS メソッドはアトミック性を保証できません。

    2.3. ロックが取得できるかどうかによって、リンクが取得できるかどうかを判断します。

    ロックを定義した後、ロックと Mysql リンクの取得を組み合わせる必要があります。 MysqlConnection と呼ばれる Mysql リンク ツール クラスは、主に 2 つの主要な機能を担当します:

    JDBC を介した Mysql とのリンクの確立;

    Mysql リンクの総数が超過しないようにロックと組み合わせる10 リクエストが大きすぎる場合。

    まず、MysqlConnection 初期化コードを見てみましょう:

    public class MysqlConnection {
      private final ShareLock lock;
      // maxConnectionSize 表示最大链接数
      public MysqlConnection(int maxConnectionSize) {
        lock = new ShareLock(maxConnectionSize);
      }
    }

    初期化中に、リンクの最大数を指定し、この値をロックに渡す必要があることがわかります。リンクの最大数が ShareLock ロックの状態値であるためです。

    次に、1 を完了するために、プライベート メソッドを作成しました:

    // 得到一个 mysql 链接,底层实现省略
    private Connection getConnection(){}

    次に、2 を実装しました。コードは次のとおりです:

    // 对外获取 mysql 链接的接口
    // 这里不用try finally 的结构,获得锁实现底层不会有异常
    // 即使出现未知异常,也无需释放锁
    public Connection getLimitConnection() {
      if (lock.lock()) {
        return getConnection();
      }
      return null;
    }
    // 对外释放 mysql 链接的接口
    public boolean releaseLimitConnection() {
      return lock.unLock();
    }

    逻辑也比较简单,加锁时,如果获得了锁,就能返回 Mysql 的链接,释放锁时,在链接关闭成功之后,调用 releaseLimitConnection 方法即可,此方法会把锁的 state 状态加一,表示链接被释放了。

    以上步骤,针对 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("获得第{}个数据库链接成功",i+1);
        }else {
          log.info("获得第{}个数据库链接失败:数据库连接池已满",i+1);
        }
      }
      log.info("模仿开始释放 mysql 链接");
      for(int i =0 ;i<12;i++){
        if(mysqlConnection.releaseLimitConnection()){
          log.info("释放第{}个数据库链接成功",i+1);
        }else {
          log.info("释放第{}个数据库链接失败",i+1);
        }
      }
      log.info("模仿结束");
    }

    以上代码逻辑如下:

    获得 Mysql 链接逻辑:for 循环获取链接,1~10 都可以获得链接,11~12 获取不到链接,因为链接被用完了;释放锁逻辑:for 循环释放链接,1~10 都可以释放成功,11~12 释放失败。

    我们看下运行结果,如下图:

    Javaリライトロックの設計構造と詳細は何ですか

    从运行的结果,可以看出,我们实现的 ShareLock 锁已经完成了 Mysql 链接共享的场景了。

    以上がJavaリライトロックの設計構造と詳細は何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。