同時学習-ReentrantLock

41126 ワード

synchronizedとReentrantLock


前回の記事では、キーワードsynchronizedを使用して同期アクセスを実現する方法について説明しました.Java 5以降、JDKは、ReentrantLockという同期アクセスを実現する別の方法を提供している.ReentrantLockは、主にこの3つの機能を追加しました.待機は中断可能で、公平なロックが実現可能で、ロックは複数の条件をバインドすることができます.
JDK 6以前はsynchronizedキーワードで同期を実現するのは便利でしたが、この同期操作はヘビー級で、プログラムの実行効率に大きく影響するので、開発者にとっては少し怖いです.しかしJDK 6以降はsynchronizedを大きく最適化し,バイアスロック,適応性スピン,軽量ロック,重量ロック,ロック粗化などの手段を導入し,現在synchronizedとReentrantLockの性能はほぼ横ばいであるため,一般的にはsynchronizedキーワードを用いることを推奨し,プログラムを簡潔かつ読みやすくする.
JDK 6より前
[外部リンク画像の転送に失敗しました(img-U 9 ZK 8 zOY-15662705080439)](https://github.com/RalfNick/PicRepository/raw/master/Thread/synchronized_jdk5_before.jpeg)]
JDK 6以降
[外部リンクピクチャの転送に失敗しました(img-P 4 v 6 a 4 pU-1566270580404)](https://github.com/RalfNick/PicRepository/raw/master/Thread/synchronized_jdk5_later.jpeg)]
しかしReentrantLockにも多くの利点があり、ReentrantLockはLockの実装クラスであり、反発する同期器であり、マルチスレッドの高競争条件下ではsynchronizedよりもReentrantLockの方が優れた性能表現を持っている.
(1)ロックの使用は比較的柔軟であるが、ロックを解除する協力動作が必要であるロックは手動でロックを取得し、解除しなければならない.synchronizedは手動でロックを解除する必要はなく、ロックはコードブロックロックにのみ適用され、synchronizedは修飾方法、コードブロックなどに使用できる.
(2)ReentrantLockの利点は、非ブロックでロックを取得しようとする特性を備えている:現在のスレッドはロックを取得しようとしているが、この時点でロックが他のスレッドに取得されていない場合、ロックが中断されてロックを取得することに成功し、保持する特性を備えている:synchronizedとは異なり、ロックを取得したスレッドは割り込みに応答することができ、ロックを取得したスレッドが割り込まれると割り込み異常が投げ出され、同時にロックが解放されるタイムアウト取得ロックの特性:指定された時間範囲内でロックを取得する;締め切り時間になってもロックが取得できない場合は、
(3)ReentrantLockを使用する場合、finallyでロックを解除することに注意してください.ロックを取得した後、最終的に解放されることを保証するために、取得ロックのプロセスをtryブロックに書かないでください.ロックを取得する際に異常が発生し、異常に放出されると同時にロックが解放されないためです.
(4)ReentrantLockは、ユーザが同じロックの場合に異なる状況に応じて待機または起動の動作を実行できるように、newConditionの方法を提供する.

2.ReentrantLockの使い方


ReentrantLockはLockインタフェースの実装クラスであり、Lockは以下のいくつかの方法を提供しています.
//  , 
void lock();

//  , ,  InterruptedException  
void lockInterruptibly() throws InterruptedException;

//  , ,true  ,false  
boolean tryLock();

//  ,  time   true,  false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

//  ,  finally  
void unlock();

//  
Condition newCondition();


2.1ロック使用


ReentrantLockロックは使用も簡単で、主にシーンによって選択されますが、tryLockを使用する方法でなければfinallyブロックでロックを解除する必要があることに注意してください.次に、ロックを取得するためのいくつかの使用例コードを示します.
public class ReentrantLockTest {

    private static final ReentrantLock reentrantLock = new ReentrantLock();
    private static int sCount = 0;

    /**
     * lock
     */
    private static class MyRunnable implements Runnable {

        @Override
        public void run() {
            reentrantLock.lock();
            try {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + " --- > " + sCount++);
                }
                Thread.sleep(3500);
            } catch (Exception e) {

            } finally {
                reentrantLock.unlock();
            }

        }
    }

    /**
     * tryLock
     */
    private static class MyRunnable1 implements Runnable {

        @Override
        public void run() {
            boolean isLock = reentrantLock.tryLock();
            if (isLock) {
                execute();
            } else {
                System.out.println(Thread.currentThread().getName() + " --- >  【 】");
            }
        }
    }

    /**
     * tryLock(long timeout, TimeUnit unit), , 
     */
    private static class MyRunnable2 implements Runnable {

        @Override
        public void run() {
            boolean isLock;
            try {
//                isLock = reentrantLock.tryLock(3, TimeUnit.SECONDS);
                isLock = reentrantLock.tryLock(4, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " --- >  【 】");
                return;
            }
            if (isLock) {
                execute();
            } else {
                System.out.println(Thread.currentThread().getName() + " --- >  【 】");
            }
        }
    }

    /**
     * lockInterruptibly  , 
     */
    private static class MyRunnable3 implements Runnable {

        @Override
        public void run() {
            try {
                reentrantLock.lockInterruptibly();
                execute();
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + " --- >  【 】");
            }
        }
    }

    private static void execute() {
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " --- > " + sCount++);
            }
        } catch (Exception e) {

        } finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        sCount = 0;
        // lock
//        test1();
        // tryLock
//        test2();
        // tryLock(long timeout, TimeUnit unit)
//        test3();
        // lockInterruptibly
        test4();
    }

    private static void test1() {
        MyRunnable runnable = new MyRunnable();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);

        thread1.start();
        thread2.start();
    }

    private static void test2() {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable1());

        thread1.start();
        thread2.start();
    }

    private static void test3() {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable2());

        thread1.start();
        thread2.start();

        //  
        thread1.interrupt();
        thread2.interrupt();
    }

    private static void test4() throws InterruptedException {
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable3());

        thread1.start();
        thread2.start();

        Thread.sleep(2000);
        //  
        thread1.interrupt();
        thread2.interrupt();
    }
}

2.2 Condition使用


ReentrantLockロックに対応するには、synchronizedが備えていない柔軟性である複数の条件があります.synchronizedのメソッドまたはブロックでは、ロックされたwait()とnotify()、notifyAll()しか使用できません.一方、ReentrantLockの1つのロックには複数の条件があり、ReentrantLockの1つのロックは1つの同期キューに対応し、複数の条件は複数の待機キューに対応する.以下ではReentrantLockの条件を用いて生産者−消費者モデルを実現する.
public class ProducerConsumer1 {

    private static final ReentrantLock sLock = new ReentrantLock();
    private static final Condition sProducerCondition = sLock.newCondition();
    private static final Condition sConsumerCondition = sLock.newCondition();

    private static final int MAX = 10;
    private static int count = 0;

    private static class Producer implements Runnable {

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                sLock.lock();
                try {
                    if (count == MAX) {
                        sProducerCondition.await();
                    }
                    count++;
                    System.out.println("  ---- > " + count);
                    Thread.sleep(500);
                    sConsumerCondition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    sLock.unlock();
                }
            }
        }
    }

    private static class Consumer implements Runnable {

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                sLock.lock();
                try {
                    if (count == 0) {
                        sConsumerCondition.await();
                    }
                    count--;
                    System.out.println("  ---- > " + count);
                    Thread.sleep(300);
                    sProducerCondition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    sLock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread producer = new Thread(new Producer());
        Thread consumer = new Thread(new Consumer());
        producer.start();
        consumer.start();

        Thread.sleep(10000);
    }
}


2.3その他の説明


ReetrantLockとsynchronizedは、再読み込みをサポートするロックです.つまり、このロックは、リソースに対するスレッドの再ロックをサポートします.しかしsynchronizedはフェアロックをサポートしておらず、ReetrantLockはフェアロックと非フェアロックをサポートしている.公平と非公平とは、要求の前後順序において、先にロックを要求した場合、必ず先にロックを取得することを指す.それは公平ロックである.逆に、ロックの取得に時間的な前後順序がなければ、後に要求されたスレッドが先にロックを取得する可能性がある.これは非公平ロックであり、一般的には非公平ロックの効率は公平ロックのメカニズムよりも優れているが、いくつかのシーンでは、時間の前後順を重視するかもしれませんが、公平な鍵は自然に良い選択です.ReetrantLockでは、同じスレッドへの再ロックがサポートされていますが、何回ロックするかは、何回ロックを解除する必要があります.これにより、ロックを正常に解除できます.