《実戦Java高同時プログラミング》読書ノート(二):JDK並発注

10040 ワード

第三章JDK並発注
3.1マルチスレッドのチームワーク:同期制御
同期制御は同時プログラムに不可欠な重要な手段である.
1、キーワードsynchronizedの機能拡張:再入錠
再ロックは、キーワードsynchronizedに完全に代わることができます.再入ロックはjavaを使用する.util.concurrent.locks.ReentrantLockクラスが実装されます.
public class ReenterLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i=0;
    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            lock.lock();
            try {
                i++;
            }finally {
                lock.unlock();
            }
        }
    }
}

キーワードsynchronizedと比較して、リエントロックには表示される操作手順があります.ロックを解除するタイミングを手動で指示する必要があります.従って、論理制御に対する再ロックの柔軟性はsynchronizedよりはるかに優れている.
再ロックは繰り返し入力でき、1つのスレッドは複数回同じロックを得ることができますが、同じ回数を解放することを覚えておいてください.
再ロックの高度な機能:
(1)割込み応答
キーワードsynchronizedの場合、1つのスレッドがロックを待っている場合、結果は2つのケースしかありません.このロックを取得して実行を続行するか、待機を維持します.再ロックは、ロックを待つ間にスレッドを中断させることができます.このような状況はデッドロックの処理に役立つ.
public class IntLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    public IntLock(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            if (lock == 1) {
                lock1.lockInterruptibly();//                 
                Thread.sleep(500);
                lock2.lockInterruptibly();
                } else {
                lock2.lockInterruptibly();
                Thread.sleep(500);
                lock1.lockInterruptibly();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if (lock1.isHeldByCurrentThread())//            
                lock1.unlock();
            if (lock2.isHeldByCurrentThread())
                lock2.unlock();
            System.out.println(Thread.currentThread().getId()+"    ");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        IntLock r1 = new IntLock(1);
        IntLock r2 = new IntLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();t2.start();
        Thread.sleep(1000);
        t2.interrupt();//  t2
    }
}

スレッドt 1とt 2が起動すると、互いに持つロックを要求することによりデッドロックとなる.t 2を中断しなければ、無期限に待ち続けることになる.t 2が中断されると、t 2はlock 1の申請を放棄し、取得したlock 2を解放し、t 1がlock 2を取得して順調に実行される.
中断すると2つのスレッドは終了しますが、本当に作業を完了したのはt 1だけです.
(2)ロック申請待ち期間
外部からの通知を待つ以外に、デッドロックを避けるには別の方法があります.時間制限待ちです.tryLock()メソッドを使用して、1回の時間制限待ちを行うことができます.
public class TimeLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                Thread.sleep(6000);
            } else {
                System.out.println("get Lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TimeLock t1 = new TimeLock();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t1);
        thread1.start();
        thread2.start();
    }
}

tryLock()メソッドは、2つのパラメータ、待ち時間、およびタイミング単位を受け入れます.ロックを占有するスレッドは6秒までロックを保持するため、別のスレッドは5秒以内にロックを取得できず、要求に失敗します.
ReentrantLock.tryLock()がパラメータを持たない場合、スレッドはロックを取得しようとし、申請がtrueに正常に戻り、申請が失敗するとすぐにfalseに戻り、待機していません.
(3)公正錠
synchronizedキーワードを使用してロック制御を行う場合、生成されるロックは非公平であり、つまり待機キューの中でランダムに1つを選択してロックを取得する.
再ロックでは、次のように公平性を設定できます.
public ReentrantLock(boolean fair)

パラメータがtrueの場合、ロックは公平であることを示します.公平ロックは時間の前後順に従い、飢餓現象が発生しないことを保証する.しかし、実現コストが高く、性能が低く、デフォルトでは不公平である.
2、ロックを再ロックする良いパートナー:Condition
Objectとwait()とnotify()の役割はほぼ同じです.
public class ReenterLockCondition implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    //  newCondition               Condition  。
    public static Condition condition = lock.newCondition();

    @Override
    public void run() {

        try {
            lock.lock();
            //await           Condition ,       
            condition.await();
            System.out.println("Thread is going on");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReenterLockCondition reenterLockCondition = new ReenterLockCondition();
        Thread thread = new Thread(reenterLockCondition);
        thread.start();
        Thread.sleep(2000);
        // signal     ,           
        lock.lock();
        //  signal  ,      Condition              
        //  ,signalAll         
        condition.signal();
        // signal     ,        ,        
        //      ,  thread      
        lock.unlock();
    }
}

3、複数のスレッドの同時アクセスを許可する:信号量(Semaphore)
信号量は対ロックの拡張である.内部ロックsynchronizedでもReentrantLockでも、一度に1つのスレッドのみが1つのリソースにアクセスできますが、信号量は複数のスレッドを指定し、同時に1つのリソースにアクセスできます.
//        ,          ,           
public Semaphore(int permits)
public Semaphore(int permits,boolean fair)  //             

//      
public void acquire()  //          
public void acquireUninterruptibly()  //     
public boolean tryAcquire()  //        ,    true,    false,   
public boolean tryAcquire(long timeout, TimeUnit unit)  //    
public void release()  //             

簡単な例です.
public class SemapDemo implements Runnable {
    //      5       
    final Semaphore semp = new Semaphore(5);

    @Override
    public void run() {
        try {
            //     
            semp.acquire();
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getId()+":done!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //         
            semp.release();
        }
    }

    public static void main(String[] args) {
        ExecutorService exec = Executors.newFixedThreadPool(20);
        final SemapDemo demo = new SemapDemo();
        //    20   ,      5       
        for (int i = 0; i < 20; i++) {
            exec.submit(demo);
        }
    }
}

4、ReadWriteLock読み書きロック
読み書き分離ロックは、ロック競合を低減し、システムのパフォーマンスを向上させるのに効果的です.
一般的に、読み取りと読み取りの間には反発せず、並列に操作することができる.読み取りと書き込み、書き込みと書き込みの間には、依然として互いにブロックされています.
public class ReadWriteLockDemo {
//    private static Lock lock = new ReentrantLock();
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = readWriteLock.readLock();    //   
    private static Lock writeLock = readWriteLock.writeLock();  //   
    private int value;

    public Object handleRead(Lock lock) throws InterruptedException {
        try {
            lock.lock();               //     
            Thread.sleep(1000);  //        ,          
            return value;
        }finally {
            lock.unlock();
        }
    }


    public void handleWrite(Lock lock, int index) throws InterruptedException {
        try {
            lock.lock();               //     
            Thread.sleep(1000);  //        ,          
            value = index;
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        final ReadWriteLockDemo demo = new ReadWriteLockDemo();
        Runnable readRunable=new Runnable() {
            @Override
            public void run() {
                try {
                    demo.handleRead(readLock);
//                    demo.handleRead(lock);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Runnable writeRunable =new Runnable() {
            @Override
            public void run() {
                try {
                    demo.handleWrite(writeLock,new Random().nextInt());
//                    demo.handleWrite(lock, new Random().nextInt());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        for (int i = 0; i < 18; i++) {
            new Thread(readRunable).start();
        }
        for (int i = 18; i < 20; i++) {
            new Thread(writeRunable).start();
        }
    }
}

以上のプログラムは、リード・ライト・ロックを使用すると2秒以上で実行できます.リード・オペレーションは並列であり、再ロックを使用すると20秒以上かかります(リード・オペレーションもブロックされます).
5、カウンター:CountDownLatch
通常、スレッド待機を制御するために使用され、バックカウンタが終了するまでスレッドを待機させて実行を開始できます.
public class CountDownLatchDemo implements Runnable {
    //           ,           
    //   10    10           CountDownLatch          
    static final CountDownLatch end = new CountDownLatch(10);
    static final CountDownLatchDemo demo = new CountDownLatchDemo();

    @Override
    public void run() {
        try {
            //        
            Thread.sleep(new Random().nextInt(10)*1000);
            System.out.println("check complete");
            //           ,      
            end.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            exec.submit(demo);
        }
        //                  ,      
        end.await();
        System.out.println("go!");
        exec.shutdown();
    }
}

6、循環柵:CyclicBarrier
CountDownLatchに似ていますが、より強力な機能を備えています.たとえば、カウンタを10に設定すると、最初のスレッド10個を揃えた後、タスクが完了するまで一緒にタスクを実行し始め、カウンタはゼロに戻り、次のスレッド10個を揃えます.
7、スレッドブロックツール類:LockSupport
スレッド内の任意の位置でスレッドをブロックできます.