JAvaのロック


一.概説
ロックはjava.util.concurrent.locksパッケージのインタフェースであり、ロック実装はsynchronizedキーワードよりも広範なロック操作を提供し、スレッド同期の問題をより優雅に処理することができます.ロックはsynchronizedよりも多くの機能を提供しています.
   1.LockとReadWriteLockは2つの大きなロックのルートインタフェースであり、Lockは実装クラスを表すReentrantLock(再ロック可能)、ReadWriteLock(読み書きロック)は実装クラスを表すReentrantReadWriteLockである.
  2.ロックインタフェースは、非ブロック構造のコンテキスト(hand-over-handおよびロック再配列アルゴリズムを含む)で使用できる、異なる意味(再入、公平など)のロック規則をサポートする.       3.ReadWriteLockインタフェースは、同様に、一部の読み取り者が共有し、書き込み者が独占できるロックを定義する.このパッケージには、ReentrantReadWriteLockの実装が1つしかありません.
   4.ロックは再ロック可能で、ロックを中断することができ、公平ロックと読み書きロックを実現することができ、読み書きロックは排他ロックであり、書き込みロックは共有ロックである.ReentrantLockも排他ロックです 
二.synchronized ロックとの違い
1.synchronizedはキーワードであり、JVMレベルであり、Lockはインタフェースであり、JDKが提供するAPIである.
2.1つのスレッドがsynchronizedロックを取得すると、他のスレッドはロックを占有するスレッドがロックを解放するまで待つしかありません.ロックが解除されるのは、a.占有ロックのスレッドがコードを実行した後、ロックの占有が解放される場合です.b.占有ロックスレッドの実行に異常が発生した場合、JVMはスレッドに自動的にロックを解除させる.c.占有ロックスレッドはWAITING状態に入ってロックを解除し、例えばこのスレッドでwait()メソッドを呼び出すなどである.
   しかし、ロックを占有するスレッドがIOを待つか、sleepメソッドを呼び出すなどの他の理由でスレッドをブロックしているが、ロックを解放していない場合、スレッドはずっと待つしかない.この場合、一定の時間だけ待つなど、スレッドを無期限に待つ方法が必要になる可能性がある(tryLock(long time,TimeUnit unit)あるいはlockInterrup 0 tibly()を人為的に中断することができ、この場合ロックが必要です.
3.複数のスレッドがファイルを読み書きする場合、読み書きと読み書きの競合が発生し、書き込みと書き込みの競合も発生するが、読み書きと読み書きの競合は発生しないが、synchronizedを用いて同期すると、複数のスレッドが読み書きのみの場合でも、ロックされたスレッドを取得してこそ読み書きが可能となり、他の線ロックが解放されるのを待つしかありません.ロックは、複数のスレッドが読み取り操作のみを行う場合、スレッド間で競合は発生しません.たとえば、ReentrantReadWriteLock()です.
4.ロックが正常に取得されたかどうかをロックで知ることができます(たとえば、ReentrantLock)が、synchronizedではできません.
5.ロック属性の違い:synchronizedは割り込み不可ロックと非公平ロックであり、ReentrantLockは割り込み操作を行い、公平ロックであるかどうかを制御することができる.
6.synchronizedはメソッドとコードブロックをロックできますが、ロックはコードブロックしかロックできません.
7.synchronizedはロックの状態を判断できませんが、ロックはスレッドがロックを受け取ったかどうかを知ることができます.
8.性能上、競合リソースが激しくなければ、両者の性能はそれほど悪くないが、競合リソースが非常に激しい場合、ロックの性能はsynchronizedよりはるかに優れている.
三.ロックインタフェース
ロックはインタフェースであり、インタフェースの実装クラスにはReentrantLockと内部クラスReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLockがあり、この章で説明するのはすべて ReentrantLock
public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

ロック()、tryLock()、tryLock(long time,TimeUnit unit)、およびlockInterruptibly()はロックを取得するために使用され、unLock()メソッドはロックを解放するために使用される .
1. lock()
ロックを取得するために使用されます.ロックが他のスレッドによって取得されている場合は待機します.ロックを使用するには、ロックを自発的に解放しなければなりません.また、異常が発生した場合、ロックは自動的に解放されません.そのため、一般的には、ロックを使用するにはtry{}catch{}ブロックで行わなければなりません.また、ロックを解放する動作はfinallyブロックで行われ、ロックが必ず解放されることを保証し、デッドロックの発生を防止します.
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        lock.lock();
        try{
            System.out.println("     !!");
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            System.out.println("     ");
            lock.unlock();
        }
    }

2. tryLock():
ロックを取得しようとしますが、このメソッドには戻り値があり、取得に成功した場合はtrueを返し、取得に失敗した場合(つまりロックが他のスレッドに取得された場合)、falseを返します.つまり、このメソッドはどうしてもすぐに戻り、ロックが取れない場合もそのまま待つことはありません. 
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        
        if(lock.tryLock()) {
            try{
                System.out.println("     !!");
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                lock.unlock();
            }
        }else {
            System.out.println("    ,    ");
        }
    
    }

3. tryLock(long time, TimeUnit unit)
tryLock()メソッドと似ていますが、このメソッドはロックが取れない場合に一定の時間待ち、期間内にロックが取れない場合はfalseに戻ります.最初にロックを取ったり、待機期間内にロックをもらったりしたらtrueに戻ります.
    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        try{
            if(lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
                System.out.println("     !!");
            }else {
                System.out.println("    ,    ");
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }

    }

4.lockInterruptibly()
この方法でロックを取得する場合、スレッドがロックの取得を待機している場合、このスレッドは、割り込み、すなわち割り込みスレッドの待機状態に応答することができる.すなわち、2つのスレッドが同時にlock.lockInterruptibly()を介してロックを取得しようとする場合、スレッドAがロックを取得し、スレッドBが待機している場合にのみ、スレッドBに対してthreadB.interrupt()を呼び出す方法は、スレッドBの待機プロセスを中断することができる.
package ReentrantLockTest;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {
    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        Test test = new Test();
        MyThread a = new MyThread(test);
        MyThread b = new MyThread(test);
        a.start();
        b.start();

        b.interrupt();
    }

    public void insert(Thread thread) throws InterruptedException {
        //  :              ,          ,   InterruptedException  
        lock.lockInterruptibly();
        try {
            System.out.println(thread.getName() + "    ");
            Thread.sleep(3000);
        } finally {
            lock.unlock();
            System.out.println(thread.getName() + "    ");
        }
    }


    static class MyThread extends Thread {
        private Test test;

        public MyThread(Test test) {
            this.test = test;
        }

        @Override
        public void run() {
            try {
                test.insert(Thread.currentThread());
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "   ");
            }
        }
    }


}

注意:1つのスレッドがロックを取得した後、interrupt()メソッドによって中断されることはありません.interrupt()メソッドを単独で呼び出すと、実行中のスレッドを中断することはできません.ブロック中のスレッドを中断するしかありません.そのため、lockInterruptibly()メソッドがロックを取得する場合、取得できない場合は、待機する場合にのみ割り込みに応答できます.synchronizedで修飾すると、1つのスレッドがロックを待機している状態では割り込みできず、ずっと待つしかありません.
5.ConditionインタフェースとnewCondition()メソッド
synchronizedキーワードはwait()とnotify/notifyAll()メソッドと組み合わせて待機/通知メカニズムを実現し、ReentrantLockクラスはConditionインタフェースとnewCondition()メソッドを利用することもできる.
synchronizedキーワードnotify/notifyAll()メソッドを使用して通知する場合、通知されるスレッドはJVMによって選択され、ReentrantLockクラスを使用してConditionインスタンスと組み合わせて「選択的通知」を実現できます.
synchronizedキーワードは、ロックオブジェクト全体に1つのConditionインスタンスしかないことに相当し、すべてのスレッドがそのインスタンスに登録されています.notifyAll()を実行する場合メソッドを使用すると、待機中のすべてのスレッドが通知され、大きな効率的な問題が発生します.一方、Conditionは、指定したConditionにスレッドオブジェクトを登録できる複数のConditionインスタンス(オブジェクトモニタ)を1つのLockオブジェクトに作成できるマルチパス通知機能を実現します.ConditionインスタンスのsignalAll()メソッドは、Conditionインスタンスに登録されているすべての待機スレッドのみを起動します.これにより、スレッド通知を選択的に行うことができ、スケジューリングスレッドでより柔軟になります.
    //                        。
    void await();

    //          、                      。
    boolean await(long time, TimeUnit unit);

    //          、                      。
    long awaitNanos(long nanosTimeout);

    //                    。
    void awaitUninterruptibly();

    //          、                      。
    boolean awaitUntil(Date deadline);

    //        。
    void signal();

    //        。
    void signalAll();

5.1 Condition実現待ち/通知メカニズム
await()文を呼び出すとスレッドがブロックされ、signal()が存在するtry文ブロックを実行してからロックが解放され、condition.await()後の文が実行されます. 
package ReentrantLockTest;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {

    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();

        MyThread a = new MyThread(service);
        a.start();

        Thread.sleep(3000);

        service.signal();
    }

    static public class MyService {

        private Lock lock = new ReentrantLock();

        public Condition condition = lock.newCondition();

        public void await() {
            lock.lock();
            try {
                System.out.println("    condition.await()  ,      ");
                condition.await();
                System.out.println("   condition.await()  ,     signal()     ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void signal() {
            lock.lock();
            try {
                System.out.println("    condition.signal()  ");
                condition.signal();
                Thread.sleep(3000);
                System.out.println("   condition.signal()  ,    await()   ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }


    static public class MyThread extends Thread {
        private MyService service;

        public MyThread(MyService service) {
            this.service = service;
        }

        @Override
        public void run() {
            service.await();
        }
    }

}

 出力:
    condition.await()  ,      
    condition.signal()  
   condition.signal()  ,    await()   
   condition.await()  ,     signal()     

5.2複数のConditionインスタンスによる待機/通知メカニズムの実装
1つのLockオブジェクトに複数のConditionインスタンスを作成することができ、あるインスタンスのsignalAll()メソッドを呼び出すと、そのConditionインスタンスに登録されているすべての待機スレッドのみが呼び出されます.
package ReentrantLockTest;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest {

    private Lock lock = new ReentrantLock();

    private Condition conditionA = lock.newCondition();

    private Condition conditionB = lock.newCondition();

    public void awaitA() {
        lock.lock();
        try {
            System.out.println("    conditionA.await()  ,      ");
            conditionA.await();
            System.out.println(" awaitA     ");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        lock.lock();
        try {
            System.out.println("    conditionB.await()  ,      ");
            conditionB.await();
            System.out.println(" awaitB     ");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalA() {
        lock.lock();
        try {
            System.out.println("     conditionA       ");
            conditionA.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalB() {
        lock.lock();
        try {
            System.out.println("     conditionB       ");
            conditionB.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}
package ReentrantLockTest;

public class Test {

    public static void main(String[] args) throws InterruptedException {

        LockTest lockTest = new LockTest();

        ThreadA a = new ThreadA(lockTest);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(lockTest);
        b.setName("B");
        b.start();

        Thread.sleep(3000);

        lockTest.signalA();

    }

    static public class ThreadA extends Thread {

        private LockTest lockTest;

        public ThreadA(LockTest lockTest) {
            this.lockTest = lockTest;
        }

        @Override
        public void run() {
            lockTest.awaitA();
        }
    }

    static public class ThreadB extends Thread {

        private LockTest lockTest;

        public ThreadB(LockTest lockTest) {
            this.lockTest = lockTest;
        }

        @Override
        public void run() {
            lockTest.awaitB();
        }
    }
}

出力: 
    conditionA.await()  ,      
    conditionB.await()  ,      
     conditionA       
 awaitA     

四.ReadWriteLockインタフェース
public interface ReadWriteLock {
    
    //   
    Lock readLock();
   
    //   
    Lock writeLock();
}

ReentrantLockは排他的ロックであり、同じ時点で1つのスレッドのみがアクセスできる.ReadWriteLockインタフェースの実装クラスReentrantReadWriteLock読み書きロックは、readLock()とwriteLock()の2つの方法を提供する.リード・ロックとライト・ロックを取得するために使用される.すなわち、ファイルのリード・ライト操作を分離し、2つのロックに分けてスレッドに割り当て、複数のスレッドが同時にリード操作を行うことができるようにする.
読み書きロックは2つのロックを維持し、1つは読み書き操作に関連するロックであり共有ロックとも呼ばれ、1つは書き込み操作に関連するロックであり排他ロックとも呼ばれる.読み書きロックと書き込みロックを分離することによって、その同時性は一般の排他ロックより大きく向上した.
複数のリード・ロック間は反発せず、リード・ロックとライト・ロックは反発し、ライト・ロックとライト・ロックは反発します(ライト・オペレーションのプロセスが発生すれば反発します).を選択します.スレッドが書き込み操作を行わない場合、読み出し操作を行う複数のスレッドはいずれもリードロックを取得できますが、書き込み操作を行うスレッドは、書き込みロックを取得してからのみ書き込み操作を行うことができます.つまり、複数のスレッドは同時に読み出し操作を行うことができますが、同じ時刻に1つのスレッドのみ書き込み操作を許可します.
1.リードロック
package ReentrantLockTest;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockTest {

    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        final ReentrantReadWriteLockTest test = new ReentrantReadWriteLockTest();

        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();

        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();

    }

    public void get(Thread thread){
        reentrantReadWriteLock.readLock().lock();

        try {

            for (int i=0;i<10;i++){
                System.out.println(thread.getName() + "       ");
                Thread.sleep(1000);
            }

            System.out.println(thread.getName() + "     ");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantReadWriteLock.readLock().unlock();
        }

    }


}

出力:
Thread-0       
Thread-1       
Thread-1       
Thread-0       
Thread-1       
Thread-0       
Thread-1       
Thread-0       
Thread-0       
Thread-1       
Thread-1       
Thread-0       
Thread-0       
Thread-1       
Thread-1       
Thread-0       
Thread-0       
Thread-1       
Thread-0       
Thread-1       
Thread-1     
Thread-0     

複数のスレッドが同時にリードロックを取得できます
2.読み書き反発
package ReentrantLockTest;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockTest {

    public static void main(String[] args) {
        LockTest lockTest = new LockTest();

        ReadThread readThread = new ReadThread(lockTest);
        ReadThread readThread2 = new ReadThread(lockTest);
        WriteThread writeThread = new WriteThread(lockTest);
        ReadThread readThread3 = new ReadThread(lockTest);
        readThread.start();
        readThread2.start();
        writeThread.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        readThread3.start();
    }


    static public class LockTest {
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

        public void read(Thread thread) {
            lock.readLock().lock();
            try {
                for (int i=0;i<5;i++){
                    System.out.println(thread.getName() + "       ");
                    Thread.sleep(1000);
                }
                System.out.println(thread.getName() + "     ");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
        }

        public void write(Thread thread) {
            lock.writeLock().lock();
            try {
                for (int i=0;i<5;i++){
                    System.out.println(thread.getName() + "       ");
                    Thread.sleep(1000);
                }
                System.out.println(thread.getName() + "     ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        }
    }

    static public class ReadThread extends Thread {
        private LockTest lockTest;

        public ReadThread(LockTest lockTest) {
            this.lockTest = lockTest;
        }

        @Override
        public void run() {
            lockTest.read(Thread.currentThread());
        }
    }


    static public class WriteThread extends Thread {
        private LockTest lockTest;

        public WriteThread(LockTest lockTest) {
            this.lockTest = lockTest;
        }

        @Override
        public void run() {
            lockTest.write(Thread.currentThread());
        }
    }
}

出力:
Thread-0       
Thread-1       
Thread-0       
Thread-1       
Thread-1       
Thread-0       
Thread-1       
Thread-0       
Thread-0       
Thread-1       
Thread-1     
Thread-0     
Thread-2       
Thread-2       
Thread-2       
Thread-2       
Thread-2       
Thread-2     
Thread-3       
Thread-3       
Thread-3       
Thread-3       
Thread-3       
Thread-3     

このことから、リードロックは共有可能であり、ライトロックはすべてのリードロックが解放された後にのみ実行されるが、ライトロックがブロックおよび取得中である場合、その後のリードロックもブロックされ、ライトロックが解放されるまで待つ必要があることがわかる.