同時プログラミングのJavaロック

7821 ワード

一、再入錠


ロックはデータを同時共有し、一貫性を保証するツールとして、JAVAプラットフォーム(synchronized(重量級)やReentrantLock(軽量級)など)で多くの実装が行われています.これらのすでに書かれたロックは、私たちの開発に便利を提供しています.
再入ロックは、再帰ロックとも呼ばれ、同じスレッドの外層関数がロックを取得した後も、内層再帰関数はロックを取得するコードを持っているが、影響を受けないことを意味する.
JAVA環境ではReentrantLockもsynchronizedも再ロック可能
synchronized:
public class Test implements Runnable {
    public  synchronized void get() {
        System.out.println("name:" + Thread.currentThread().getName() + " get();");
    }

    public synchronized  void set() {
        System.out.println("name:" + Thread.currentThread().getName() + " set();");
        get();
    }

    @Override
    public void run() {
        set();
    }

    public static void main(String[] args) {
        Test ss = new Test();
        new Thread(ss).start();
        new Thread(ss).start();
        new Thread(ss).start();
        new Thread(ss).start();
    }
}

ReentrantLock:
public class Test02 extends Thread {
    ReentrantLock lock = new ReentrantLock();
    public void get() {
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getId());
        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }
    public void set() {
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getId());
            get();
        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }
    @Override
    public void run() {
        set();
    }
    public static void main(String[] args) {
        Test ss = new Test();
        new Thread(ss).start();
        new Thread(ss).start();
        new Thread(ss).start();
    }

}

通常、関数が自分で完了するとロックが解放されます.以上の2つの方法は、ロックが取得された関数でロックが必要な関数を再呼び出し、再入力性がなければ、新しいロックが必要な関数を呼び出すと、デッドロックになります.再利用可能なロックは、再帰呼び出しにおいて非常に重要です.

二、読み書きロック


プログラムには、共有リソースの読み取りおよび書き込み操作が含まれており、書き込み操作は読み取り操作ほど頻繁ではありません.書き込み操作がない場合、2つのスレッドが1つのリソースを同時に読むのは問題ないので、複数のスレッドが共有リソースを同時に読み取ることができるようにする必要があります.
しかし、これらの共有リソースを書きたいスレッドがある場合は、他のスレッドがそのリソースを読み書きするべきではありません(つまり、読み取り-読み取りが共存し、読み取り-書き込みが共存できず、書き込み-書き込みが共存できません).
この問題を解決するには、読み取り/書き込みロックが必要です.Java 5はjavaにあります.util.concurrentパッケージにはすでに読み書きロックが含まれています.
public class Cache {
    static Map map = new HashMap();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();

    //  key value
    public static final Object get(String key) {
        
        try {
            r.lock();
            System.out.println(" ,key:" + key + "  ");
            Thread.sleep(100);
            Object object = map.get(key);
            System.out.println(" ,key:" + key + "  ");
            System.out.println();
            return object;
        } catch (InterruptedException e) {

        } finally {
            r.unlock();
        }
        return key;
    }

    //  key value, value
    public static final Object put(String key, Object value) {
        
        try {
            w.lock();
            System.out.println(" ,key:" + key + ",value:" + value + " .");
            Thread.sleep(100);
            Object object = map.put(key, value);
            System.out.println(" ,key:" + key + ",value:" + value + " .");
            System.out.println();
            return object;
        } catch (InterruptedException e) {

        } finally {
            w.unlock();
        }
        return value;
    }

    //  
    public static final void clear() {
        try {
            w.lock();
            map.clear();
        } finally {
            w.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    Cache.put(i + "", i + "");
                }

            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    Cache.get(i + "");
                }

            }
        }).start();
    }
}

三、悲観錠、楽観錠


1、楽観錠


常にコンカレントの問題は発生しないと考えられており、データを取り出すたびに他のスレッドがデータを修正しないと考えられているため、鍵はかけられないが、更新時に他のスレッドがそれ以前にデータを修正したかどうかを判断し、バージョン番号メカニズムやCAS操作で実現するのが一般的である.
バージョン方式:一般的にデータテーブルにデータバージョン番号バージョンフィールドを追加し、データが変更された回数を表し、データが変更された場合、バージョン値が1つ追加されます.スレッドAがデータ値を更新しようとすると、データの読み込みと同時にバージョン値が読み出され、更新がコミットされると、先ほど読み込んだバージョン値が現在のデータベースのバージョン値と等しい場合に更新されます.そうしないと、更新が成功するまで更新操作を再試行します.
コアSQL文:
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
CAS操作方式:compare and swapまたはcompare and setであり、3つの操作数、データが存在するメモリ値、予想値、新しい値に関連する.更新が必要な場合は、現在のメモリ値が以前に取った値と等しいかどうかを判断し、等しい場合は新しい値で更新し、失敗した場合は再試行し、一般的にはスピン操作、すなわち継続的な再試行である.

2、悲観ロック


常に最悪の場合を想定し、データを取得するたびに他のスレッドが変更されると考えられるため、ロック(リードロック、ライトロック、ローロックなど)が加算され、他のスレッドがデータにアクセスしようとすると、ブロックして停止する必要があります.ロー・ロック、リード・ロック、ライト・ロックなど、データベースに依存して実現できるのは、操作前にロックされ、Javaではsynchronizedの考え方も悲観的なロックです.

四、原子類


java.util.concurrent.atomicパッケージ:原子クラスの小さなツールパッケージで、単一の変数でロックを解除するスレッドの安全なプログラミングをサポートします
原子変数クラスは汎化volatile変数に相当し,原子のおよび条件付きの読み取り−変更−書き込み動作をサポートすることができる.
AtomicIntegerはintタイプの値を表し、getとsetメソッドを提供します.これらのVolatileタイプのint変数は読み取りと書き込みで同じメモリの意味を持っています.また、原子のcompareAndSetメソッド(このメソッドが正常に実行されると、volatile変数の読み取り/書き込みと同じメモリ効果が実現される)、原子の追加、増分、減算などの方法も提供されます.AtomicIntegerは表面的には拡張されたCounterクラスによく似ていますが、競合が発生した場合、ハードウェアの同時サポートを直接利用するため、より高い伸縮性を提供することができます.
なぜ原子類CAS:Compare and Swapがあるのか、すなわち比較して交換する.
jdk 5はjavaを追加して発注する.util.concurrent.*,その次のクラスはCASアルゴリズムを用いてsynchronouse同期ロックとは異なる楽観的なロックを実現した.mysqlのversionフィールドに似た楽観的なロック
同じ変数が複数のスレッドにアクセスされる場合は、パッケージ内のクラスを使用できます.
  • AtomicBoolean
  • AtomicInteger
  • AtomicLong
  • AtomicReference

  • 五、CAS無ロックモード


    CAS:Compare and Swap、すなわち比較再交換.jdk 5はjavaを追加して発注する.util.concurrent.*,その次のクラスはCASアルゴリズムを用いてsynchronouse同期ロックとは異なる楽観的なロックを実現した.JDK 5以前のJava言語はsynchronizedキーワードで同期を保証していたが、これは独占ロックであり、悲観ロックでもある.

    1、CASアルゴリズムの理解

  • (1)比較交換(以下CASと略す)を使用すると、ロックよりもプログラムが複雑に見えます.しかし、その非閉塞性のため、デッドロック問題に対して生まれつき免疫し、スレッド間の相互影響もロックに基づく方法よりはるかに小さい.さらに重要なのは、ロックレス方式を使用すると、ロック競合によるシステムオーバーヘッドが全くなく、スレッド間の頻繁なスケジューリングによるオーバーヘッドもないため、ロックベース方式よりも優れた性能を有することです.
  • (2)ロックなしの利点:第一に、高同時性の場合、ロックされたプログラムよりも優れた性能を有する.第二に、それは生まれながらにして免疫をロックしています.
  • (3)CASアルゴリズムのプロセスは、3つのパラメータCAS(V,E,N):Vは更新する変数を表し、Eは予想値を表し、Nは新しい値を表す.V値がE値に等しい場合のみ、Vの値をNに設定します.V値とE値が異なる場合は、他のスレッドが更新されていることを示します.現在のスレッドは何もしません.最後に、CASは現在のVの真の値を返します.(Vは更新する変数メインスレッドの値メインメモリ、Eは予想値ローカルメモリワークメモリ、Nは新値)
  • .
  • (4)CAS操作は楽観的な態度で行われ,いつも自分が操作を成功させることができると考えている.複数のスレッドが同時にCASを使用して1つの変数を操作すると、1つだけ勝って更新に成功し、残りは失敗します.失敗したスレッドは一時停止されず、失敗を通知され、再試行が許可されます.もちろん、失敗したスレッドが操作を放棄することも許可されます.このような原理に基づいて,CAS動作はロックがなくても,現在のスレッドに対する他のスレッドの干渉を発見し,適切な処理を行うことができる.
  • (5)簡単に言えば、CASはこの変数が今どのようになっていると思うかという期待値を追加する必要があります.変数があなたが想像していたほどでなければ、他の人に修正されたことを意味します.もう一度読み直して、もう一度修正してみればいいです.
  • (6)ハードウェアの面では,ほとんどの現代プロセッサが原子化CAS命令をサポートしている.JDK 5.0以降、仮想マシンは、この命令を使用して、同時動作および同時データ構造を実現することができ、このような動作は、仮想マシンにおいてどこにでもあると言える.

  • 2、CASの欠点


    CASにはABA問題という明らかな問題がある.
    質問:変数Vが初めて読み込まれたときがAで、付与の準備中にAであることが確認された場合、他のスレッドによって値が変更されていないことを説明できますか?
    この間にBに変更され、Aに変更された場合、CAS操作は変更されたことがないと勘違いします.この場合、javaおよび発注には、変数値のバージョンを制御することによってCASの正確性を保証するタグ付き原子参照クラスAtomicStampedReferenceが提供される.
    個人ブログカタツムリ