前言
上一篇文章 Java并发编程之synchronized(二) 中提到了重量级锁的主要组成部分有Owner、EntryList、WaitSet三部分,调用wait方法的线程就会进入WaitSet中进行等待。
正确姿势
public class Test {
private final static Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (LOCK) {
try {
System.out.println("t1 ready wait");
LOCK.wait();
System.out.println("t1 end wait");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "t1").start();
new Thread(() -> {
synchronized (LOCK) {
System.out.println("t2 ready notify");
LOCK.notify();
System.out.println("t2 end notify");
}
}, "t2").start();
}
}
上面创建了两个线程t1和t2,t1线程获取到锁之后Monitor中就会把Owner标记为t1,然后线程t1调用了wait()方法就会进入WaitSet中进行等待,同时会释放锁也就是把Owner置为null。这时线程t2去获取锁发现Owner是null所以就把Owner标记为t2,然后t2调用了notify方法,等执行完成之后就会去WaitSet中唤醒等待的线程t1,所以线程t1就会继续执行,最后的输出结果如下:
t1 ready wait
t2 ready notify
t2 end notify
t1 end wait
错误姿势
在上面的代码中不管是wait还是notify方法,都是在获取到锁之后调用的,那么如果在获取到锁之前调用会有什么问题呢?
- 错误地调用
wait
public class Test {
private final static Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
LOCK.wait();
}
}
- 错误地调用
notify
public class Test {
private final static Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (LOCK) {
try {
System.out.println("t1 ready wait");
LOCK.wait();
System.out.println("t1 end wait");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "t1").start();
Thread.sleep(1000);
LOCK.notify();
}
}
上面两种方式的结果都是会抛出IllegalMonitorStateException异常
因为调用
wait方法会让线程进入WaitSet去等待,而只有通过synchronized给对象加锁时才会去操作系统申请Monitor对象并进行关联,然后才能够进入WaitSet中,没有加锁的情况下根本没有Monitor,就更不用说进入WaitSet等待了。
同样地,notify的作用是去唤醒WaitSet中的线程,而不管是WaitSet还是EntryList中的线程,都不是处于活跃状态,只有Owner中的线程才能够去进行唤醒,所以没有获取锁的线程根本没有这个能力去唤醒。
上面两种都可以说是加锁前的线程和Monitor毫无关系。
小秘密
wait()方法其实调用的是wait(0),还有一个重载方法是wait(long timeout, int nanos),查看源码可以发现这个nanos其实不是一个准确值,只要nanos大于0,就会让timeout加1(毫秒)。
public final void wait(long timeout, int nanos) throws InterruptedException {
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
另外还有一个类似的方法就是sleep,当nanos大于500000或者millis等于0而nanos不等于0,就让millis加1(毫秒)
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
能力一般,水平有限,不足之处还望指正,反正也不一定改