【JUC】LockSupport浅分析

20294 ワード

目次
  • wait/notify
  • LockSupport
  • wait/notifyとLockSupportを比較
  • LockSupport注意事項
  • 参考文献
  • LockSupportはJava 6が導入したツール類で、簡単で柔軟で、応用が広いです。LockSupportがない前にスレッドの吊り上げと目覚ましは私達は全部Objectのwaitとnotify/notifyAll方法で実現します。
    私たちは例をあげて両者の違いを説明します。
    wait/notify
    スレッドtがある業務ロジックを実行した後、waitを呼び出して自分をブロックします。メインスレッドがnotifyメソッドを呼び出してスレッドAを起動し、スレッドAはその後自分で実行した結果を印刷します。
    public class WaitTest {
        public static void main(String[] args) throws Exception {
            final Object obj = new Object();
            Thread t = new Thread(() -> {
                int sum = IntStream.range(0, 11).sum();
                try {
                    obj.wait();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(sum);
            });
            t.start();
            //     ,    t      ,   wait  
            Thread.sleep(1000);
            obj.notify();
        }
    }
    
    このコードを実行すると、このエラーを発見するのは難しくないです。
    java.lang.IllegalMonitorStateException
    	at java.base/java.lang.Object.wait(Native Method)
    	at java.base/java.lang.Object.wait(Object.java:328)
    	at com.ctrip.flight.test.concurrent.WaitTest.lambda$main$0(WaitTest.java:16)
    	at java.base/java.lang.Thread.run(Thread.java:834)
    55
    Exception in thread "main" java.lang.IllegalMonitorStateException
    	at java.base/java.lang.Object.notify(Native Method)
    	at com.ctrip.flight.test.concurrent.WaitTest.main(WaitTest.java:25)
    
    理由は簡単です。waitとnotify/notifyAll方法は同期コードブロックでしか使えません。コードを次のように修正すれば正常に動作します。
    public class WaitTest {
        public static void main(String[] args) throws Exception {
            final Object obj = new Object();
            Thread t = new Thread(() -> {
                int sum = IntStream.range(0, 11).sum();
                try {
                    synchronized (obj) {
                        obj.wait();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(sum);
            });
            t.start();
            //     ,    A      ,   wait  
            Thread.sleep(1000);
            synchronized (obj) {
                obj.notify();
            }
        }
    }
    
    上記の例では、mainスレッドsleepで1秒待ちのスレッドがwait操作に実行されるまで、mainスレッドがsleepでない場合、tスレッドより先にnotify方法を実行するとどうなるかを考えてみた。
    public class WaitTest {
        public static void main(String[] args) throws Exception {
            final Object obj = new Object();
            Thread t = new Thread(() -> {
                int sum = IntStream.range(0, 11).sum();
                try {
                    synchronized (obj) {
                        obj.wait();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(sum);
            });
            t.start();
            //      ,    t      ,   wait  
            // Thread.sleep(1000);    t     wait  
            synchronized (obj) {
                obj.notify();
            }
        }
    }
    
    何回か実行すると、JVMプロセスが終了しないことが分かります。mianスレッドはnotifyを実行した後に終了しました。この時、tスレッドはwaitに実行されましたが、notifyを受け取っていません。tスレッドはずっと待ち状態です。tはdaemenスレッドではないので、JVMプロセスは終了しません。tスレッドは、まず使用synchronizedobjオブジェクトのロックを取得し、mainスレッドがnotifyを実行できなくなり、ブロックsynchronized (obj)操作に続き、tスレッド実行wait動作は、Objのロックを解除し、自身をブロックし、mainスレッドが新たにobjのロックを取得し、notify方法を実行します。synchronizedコードブロックを脱退して、Objロックを解除した後、tスレッドはobj.wait()においてロックを再取得し、その後も継続して実行する。
    LockSupport
    以下、LockSupportを使って同じ機能を実現します。
    public class LockSupportTest {
        public static void main(String[] args) throws Exception {
            Thread t = new Thread(() -> {
                int sum = IntStream.range(0, 11).sum();
                LockSupport.park();
                System.out.println(sum);
            });
            t.start();
            LockSupport.unpark(t);
        }
    }
    
    このコードはいずれにしても、JVMプロセスは正常に終了し、メインスレッドのLockSupport.unpark(t);tスレッドより先にLockSupport.park();実行されます。
    wait/notifyとLockSupportの対比
    比較すると、LockSupportはwait/notifyに比べて2つの大きな利点があることが分かります。
  • LockSupportは同期コードブロックを必要とせず、wait/notifyはsynchronizedを使ってコードブロックを同期する必要があり、同期した2つのスレッド間も共有の同期オブジェクトを維持する必要がなく、スレッド間のデカップリングを実現した。
  • unpark関数はparkより先に呼び出すことができますので、スレッド間の実行の先着順を心配する必要はなく、waitはnotifyより先に呼び出す必要があります。
  • LockSupport注意事項
  • マルチ呼び出し・unpark一度呼び出しunpark方法効果と同様に、例えばスレッドAが二回連続して呼び出されるLockSupport.unpark(B)方法がスレッドBを呼び覚まし、スレッドBが二回起動するLockSupport.park()方法は、スレッドBが依然としてブロックされます。
  • 参考文献
    自分で書いて鍵をかけます。