【JUC】LockSupport浅分析
目次wait/notify LockSupport wait/notifyとLockSupportを比較 LockSupport注意事項 参考文献 LockSupportはJava 6が導入したツール類で、簡単で柔軟で、応用が広いです。LockSupportがない前にスレッドの吊り上げと目覚ましは私達は全部Objectのwaitとnotify/notifyAll方法で実現します。
私たちは例をあげて両者の違いを説明します。
wait/notify
スレッドtがある業務ロジックを実行した後、waitを呼び出して自分をブロックします。メインスレッドがnotifyメソッドを呼び出してスレッドAを起動し、スレッドAはその後自分で実行した結果を印刷します。
LockSupport
以下、LockSupportを使って同じ機能を実現します。
wait/notifyとLockSupportの対比
比較すると、LockSupportはwait/notifyに比べて2つの大きな利点があることが分かります。LockSupportは同期コードブロックを必要とせず、wait/notifyはsynchronizedを使ってコードブロックを同期する必要があり、同期した2つのスレッド間も共有の同期オブジェクトを維持する必要がなく、スレッド間のデカップリングを実現した。 unpark関数はparkより先に呼び出すことができますので、スレッド間の実行の先着順を心配する必要はなく、waitはnotifyより先に呼び出す必要があります。 LockSupport注意事項マルチ呼び出し・ 参考文献
自分で書いて鍵をかけます。
私たちは例をあげて両者の違いを説明します。
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スレッドは、まず使用synchronized
objオブジェクトのロックを取得し、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つの大きな利点があることが分かります。
unpark
一度呼び出しunpark
方法効果と同様に、例えばスレッドAが二回連続して呼び出されるLockSupport.unpark(B)
方法がスレッドBを呼び覚まし、スレッドBが二回起動するLockSupport.park()
方法は、スレッドBが依然としてブロックされます。自分で書いて鍵をかけます。