Java同時Concurrentパッケージのロック(二)——スピン/ブロッキング/再入力可能

5706 ワード

Javaおよび発注Concurrentのパケット構造は、-原子変数クラス-ロック-collection同時集合フレームワーク-excutorスレッドプール-同期ツールの5つの部分に分けることができます.
本論文では,スピン,閉塞,再入可能,公平ロック,非公平ロックなどのロックのいくつかの原理と特徴を紹介する.
スピン
たとえば、synchronizedキーワードを使用して、ロックオブジェクトが使用されているかどうか、または解放されているかどうかをマークする簡単なロッククラスロックを実現することができます.
public class Lock {

    private AtomicReference lockedBy = new AtomicReference();

    public void lock() throws InterruptedException {
        Thread current = Thread.currentThread();
        while (!lockedBy.compareAndSet(null, current)) {

        }
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        lockedBy.compareAndSet(current, null);
    }

}

CAS原子操作を使用し、ロックすると予測前の状態がnullになり、ownerが現在のスレッドに設定されます.ロックを解除すると、予測前のステータスが現在のスレッドになり、ownerがnullに設定されます.これにより、最初のスレッドがロックされると、2番目のスレッドもロックされると、最初のスレッドがロックされるまでwhileでループし、2番目のスレッドが本当に実行を開始することができます.スピンロックは,現在のスレッドをループ体を絶えず実行するだけで,スレッド状態の変化を行わないので,応答速度がより速くなる.しかし、スレッド数が増加し続けると、各スレッドが実行され、CPU時間がかかるため、パフォーマンスの低下が顕著になる.スレッドの競合が激しくなければ、ロックの期間は維持されます.スピンロックの使用に適しています.
ブロッキング
ブロックロックは、スレッドをブロック状態にして待機させ、対応する信号(起動、時間)を取得すると、スレッドの準備準備準備完了状態に入ることができ、準備完了状態のすべてのスレッドは、競争を通じて、実行状態に入ることができます.ブロックを含める方法は、synchronizedキーワード、ReentrantLock、wait()otify()、LockSupport.park()/unpart()です.ブロックロックの利点は、ブロックされたスレッドがCPU時間を消費しないことであり、CPU占有率が高すぎることはないが、アクセス時間およびリカバリ時間はスピンロックよりもやや遅いことである.競合が激しい場合、ブロッキングロックの性能はスピンロックよりも明らかに高い.スレッド競合が激しくない場合はスピンロック,競合が激しい場合はブロックロックを用いる.
再入力可能
再入力可能とは、synchronizedを例にとると、現在のスレッドがコード内のsynchronized同期ブロックに入り、そのため、同期ブロックが使用する同期オブジェクトのロックが得られると、このスレッドは、同じオブジェクトによって同期された別のコードブロックに入ることができることを意味する.例えば、下の例:
public class Reentrant1 {

    public synchronized void step1(){
        step2();
    }

    public synchronized void step2(){
        // do something
    }

}

スレッドがオブジェクト上のロックをすでに持っている場合は、そのオブジェクトに同期されたすべてのコードブロックにアクセスできます.これが再入可能です.スレッドは、すでに所有しているロックが同期しているコードブロックのいずれかに入ることができます.上で定義したロッククラスを使用すると、次のようになります.
public class Reentrant2 {

    private Lock lock = new Lock();

    public void step1() throws InterruptedException {
        lock.lock();
        try {
            step2();
        } finally {
            lock.unlock();
        }
    }

    public void step2() throws InterruptedException {
        lock.lock();
        try {
            // do something
        } finally {
            lock.unlock();
        }
    }

}

ステップp 1メソッドを実行すると、まずlockオブジェクトに対してロックを実行し、次にstep 2メソッドに入り、ステップp 2メソッドの第1ステップもlockオブジェクトに対してロックを実行する.ここで、第2ステップは、ロッククラスのwhileにおける条件判断により、現在のスレッドをループに入れる.どのスレッドか判断していないので.次のように変更します.
public class Lock {

    private AtomicReference lockedBy = new AtomicReference();
    private AtomicInteger lockCount;

    public void lock() throws InterruptedException {
        Thread current = Thread.currentThread();
        while (!lockedBy.compareAndSet(null, current) && !lockedBy.compareAndSet(current, current)) {

        }
        lockCount.incrementAndGet();
    }

    public void unlock() {
        Thread current = Thread.currentThread();
        if (lockCount.decrementAndGet()==0) {
            lockedBy.compareAndSet(current, null);
        }
    }

}

このように修正して前オブジェクトのロックのスレッドかどうかを判断し,もしそうであれば通過を許可し,次にオブジェクトのロック回数lockCountを1加算する.unlockメソッドでは、呼び出すたびに1が減算されます.オブジェクトのロック回数が0の場合にのみ、オブジェクトのロックの制御を解除できます.
公平と非公平
公平と非公平はReentrantロックで詳しく説明されています.