我们以例子来说明两者之间的区别
线程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秒钟等待t线程执行到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不是daemon线程,JVM进程不会退出(只有当当前运行的所有线程都是deamon线程时JVM才会退出)。t线程要先使用synchronized
获取obj对象的锁,使得main线程无法执行notify,阻塞在synchronized (obj)
操作,接着t线程执行wait
操作释放obj的锁,并将自身阻塞起来,main线程重新获得obj的锁,执行notify方法,退出synchronized代码块释放obj锁后t线程在obj.wait()
处重新获取锁,继续往下执行。
下面使用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进程都可以正常退出,即使main线程的LockSupport.unpark(t);
先于t线程的LockSupport.park();
执行。
通过对比可以发现LockSupport相比wait/notify具有两大优势:
unpark
方法和调用一次unpark
方法效果一样,例如线程A连续调用两次LockSupport.unpark(B)
方法唤醒线程B,然后线程B调用两次LockSupport.park()
方法, 线程B依旧会被阻塞;自己动手写把”锁”—LockSupport深入浅出