面経-楽観ロックと悲観ロック

5049 ワード

悲観ロック
いつも最悪の場合、データを取りに行くたびに他の人が修正すると思っているので、データを取るたびに鍵がかかっています.他の人がこのデータを手に入れようとするとロックが手に入るまでブロックされます(共有リソースは毎回1つのスレッドのみに使用され、他のスレッドはブロックされ、使い終わった後に他のスレッドに譲渡される).従来の関係型データベースでは、行ロック、表ロックなど、リードロック、ライトロックなど、多くのロックメカニズムが用いられており、いずれも操作を行う前にロックされている.JavaではsynchronizedやReentrantLockなどの独占ロックが悲観ロック思想の実現である.  
楽観ロック
常に最良の場合を想定して、データを取りに行くたびに他の人は修正しないと思っているので鍵はかけませんが、更新する際にその期間に他の人がこのデータを更新したかどうかを判断し、バージョン番号メカニズムとCASアルゴリズムを使って実現することができます.楽観的なロックは、write_と同様にデータベースが提供するように、マルチリードアプリケーションのタイプに適しています.conditionメカニズムは、実際には楽観的なロックを提供しています.Javaでjava.util.concurrent.atomicパケットの下の原子変数クラスは,楽観的ロックの実装方式CASを用いて実装される. 
楽観的ロックの一般的な2つの実装方法
1.バージョン番号メカニズムは、通常、データテーブルにデータバージョン番号versionフィールドを追加し、データが変更された回数を表し、データが変更された場合、version値が1つ追加されます.スレッドAがデータ値を更新しようとすると、データの読み込みと同時にバージョン値が読み出され、更新がコミットされると、先ほど読み込んだバージョン値が現在のデータベースのバージョン値と等しい場合に更新されます.そうしないと、更新が成功するまで更新操作を再試行します.
簡単な例を挙げます.データベース内の勘定科目情報テーブルにversionフィールドがあり、現在の値は1です.現在の勘定科目残高フィールド(balance)は$100です.
オペレータAはこれを読み出し(version=1)、その勘定科目残高から$50($100-$50)を差し引く.オペレータAの操作中に、オペレータBもこのユーザ情報(version=1)を読み込み、その勘定科目残高から$20($100-$20)を差し引く.オペレータAは修正作業を完了し、データバージョン番号を1つ加算する(version=2)勘定科目控除後残高(balance=$50)とともにデータベース更新にコミットされると、コミットされたデータバージョンがデータベース記録の現在のバージョンよりも大きいため、データが更新され、データベース記録versionが2に更新される.オペレータBは操作を完了し、バージョン番号を1つ追加(version=2)してデータベースにデータをコミットしようとする(balance=$80)ただし、この時点でデータベースレコードバージョンと比較すると、オペレータBが発行するデータバージョン番号は2であり、データベースレコードの現在バージョンも2であり、「現在最後に更新されたversionはオペレータの最初のバージョン番号と等しい「の楽観的ロックポリシーであるため、オペレータBの提出は却下される.これにより、オペレータBがversion=1に基づく古いデータで修正した結果でオペレータAの操作結果を上書きする可能性が回避される.
2.CASアルゴリズムであるcompare and swap(比較と交換)は、有名な無ロックアルゴリズムである.無ロックプログラミングは、ロックを使用しない場合にマルチスレッド間の変数同期を実現すること、つまりスレッドがブロックされていない場合に変数の同期を実現することから、非ブロック同期(Non-blocking Synchronization)とも呼ばれる.CASアルゴリズムは、3つのオペランドに係わる
読み書きを必要とするメモリ値Vを比較する値Aが書込まれる新しい値Bであり、Vの値がAに等しい場合にのみ、CASは新しい値Bを原子的に更新し、そうでなければ何の操作も実行しない(比較と置換は原子操作である).一般的にはスピン操作、すなわち繰り返しの再試行である.
楽観的なロックの欠点
1 ABA問題変数Vが初めて読み込まれたときにA値であり、付与の準備中にA値であることが確認された場合、他のスレッドによって値が変更されていないことを説明できますか?明らかにできません.この間、その値が他の値に変更され、Aに変更される可能性があるので、CAS操作は変更されたことがないと勘違いします.この問題はCAS操作の「ABA」問題と呼ばれている.
JDK 1.5以降のAtomicStampedReferenceクラスでは、compareAndSetメソッドは、まず、現在の参照が所望の参照に等しいかどうかを確認し、現在のフラグが所望のフラグに等しいかどうかを確認し、すべてが等しい場合、その参照とフラグの値を所定の更新値に原子的に設定する機能を提供します.
2サイクル時間長オーバーヘッド大スピンCAS(つまり成功しない場合は成功するまでサイクルを継続する)長時間成功しない場合、CPUに非常に大きな実行オーバーヘッドをもたらす.JVMがプロセッサが提供するpause命令をサポートできれば、効率は一定に向上し、pause命令には2つの作用があり、第一に、流水ライン実行命令を遅らせることができる(de-pipeline)は、CPUが実行リソースを消費しすぎないようにし、遅延時間は具体的な実装のバージョンに依存し、一部のプロセッサでは遅延時間はゼロである.第2に、サイクルを終了する際にメモリ順序の衝突(memory order violation)によってCPUフローラインがクリアされることを回避し、CPUの実行効率を向上させることができる.
3共有変数が1つしか保証できない原子操作CASは単一の共有変数に対してのみ有効であり、操作が複数の共有変数にまたがる場合CASは無効である.しかしJDK 1.5からAtomicReferenceクラスを提供して参照オブジェクト間の原子性を保証し、複数の変数を1つのオブジェクトに配置してCAS操作を行うことができます.したがって、ロックを使用するか、AtomicReferenceクラスを使用して複数の共有変数を1つの共有変数に結合して操作することができます. 
楽観ロックの例:
/**
 *    
 * 
 *   :     value,         ,       ,        
 *        synchronized  ,    .
 *                 
 *
 */
public class OptimisticLock {
    public static int value = 0; //             

    /**
     * A        
     */
    public static void invoke(int Avalue, String i)
            throws InterruptedException {
        Thread.sleep(1000L);//      
        if (Avalue != value) {//  value  
            System.out.println(Avalue + ":" + value + "A     ,   ");
            value--;
        } else {
            Avalue++;//     
            value = Avalue;;//     
            System.out.println(i + ":" + value);
        }
    }

    /**
     * B        
     */
    public static void invoke2(int Bvalue, String i)
            throws InterruptedException {
        Thread.sleep(1000L);//      
        if (Bvalue != value) {//  value  
            System.out.println(Bvalue + ":" + value + "B     ,   ");
        } else {
            System.out.println("B:  value  ,value="+Bvalue);
        }
    }

    /**
     *   ,    :B       value         
     */
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {//A  
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) {
                        int Avalue = OptimisticLock.value;//A   value
                        OptimisticLock.invoke(Avalue, "A");
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {//B  
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) {
                        int Bvalue = OptimisticLock.value;//B   value
                        OptimisticLock.invoke2(Bvalue, "B");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

}

文章の一部はhttps://blog.csdn.net/qq_34337272/article/details/81072874