Java并发编程之wait、nofity

82 阅读2分钟

4ca4398f07c71a4f0c3b72c9adbc9e1d.jpeg

前言

上一篇文章 Java并发编程之synchronized(二) 中提到了重量级锁的主要组成部分有OwnerEntryListWaitSet三部分,调用wait方法的线程就会进入WaitSet中进行等待。

未命名文件 (8).png

正确姿势

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();
    }
}

上面创建了两个线程t1t2t1线程获取到锁之后Monitor中就会把Owner标记为t1,然后线程t1调用了wait()方法就会进入WaitSet中进行等待,同时会释放锁也就是把Owner置为null。这时线程t2去获取锁发现Ownernull所以就把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异常

Snipaste_2023-04-18_21-05-51.png 因为调用wait方法会让线程进入WaitSet去等待,而只有通过synchronized给对象加锁时才会去操作系统申请Monitor对象并进行关联,然后才能够进入WaitSet中,没有加锁的情况下根本没有Monitor,就更不用说进入WaitSet等待了。

同样地,notify的作用是去唤醒WaitSet中的线程,而不管是WaitSet还是EntryList中的线程,都不是处于活跃状态,只有Owner中的线程才能够去进行唤醒,所以没有获取锁的线程根本没有这个能力去唤醒。

上面两种都可以说是加锁前的线程和Monitor毫无关系。

小秘密

wait()方法其实调用的是wait(0),还有一个重载方法是wait(long timeout, int nanos),查看源码可以发现这个nanos其实不是一个准确值,只要nanos大于0,就会让timeout1(毫秒)。

public final void wait(long timeout, int nanos) throws InterruptedException {
    if (nanos > 0) {
        timeout++;
    }
    wait(timeout);
}

另外还有一个类似的方法就是sleep,当nanos大于500000或者millis等于0nanos不等于0,就让millis1(毫秒)

public static void sleep(long millis, int nanos)
throws InterruptedException {
    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }
    sleep(millis);
}

能力一般,水平有限,不足之处还望指正,反正也不一定改