为什么要有 wait 和 notify

本贴最后更新于 2161 天前,其中的信息可能已经时过境迁

除了 synchronizedvolatile 这两个关键字和对象头文件中的锁标志位之外,Object 类中还有三个方法 wait(),notify()和notifyAll().

为什么要有这三个方法

看一下下面这段代码

public void synchronized printThreadName(Thread thread){
    System.out.println("thread's name = " + thread.getName()+ "\n假装这是一个长时间的操作");
}

当三个线程同时执行这段代码,只会有一个线程能够获得执行条件,其他两个线程被阻塞,但是这两个线程一直盯着占得先机的线程释放锁,然后再争夺锁的所有权.这是一种很激烈的行为,长时间地阻塞非常耗费 cpu 的资源.所以为了防止 cpu 爆肝式的竞争锁,wait(),notifynotifyAll() 这三个方法应运而生.如果锁已经被夺走了,那我们就去休息,等释放了锁再叫醒我们去争夺锁,大概就是这么一个意思.

使用

例如,我们写一个 Lock

public class Lock{
    private boolean islocked = false;
    public synchronized void lock() throws InterruptedException{
        while(locked){
            wait();
        }
        islocked = true;
    }
    
    public synchronized void unlock(){
        if(!islocked)
            throw new IllegalMonitorStateException();
        islocked = false;
        notifyAll();
    }
}

这段代码只是为了演示,如果非当前线程在当前线程调用了 lock 方法之后调用了 unlock 方法肯定会出问题(请忽略).

想象一下这段代码为线程之间的行为做的限制:线程 A 和线程 B 抢占 lock() 方法,线程 A 占得先机,此时 lockedfalse,然后将 islocked 改成 true,退出同步代码块执行自己的逻辑,接着线程 B 抢占到锁,进行 lock() 方法,发现已经被别人锁了,开始 wait(),一定要注意这个 wait(),虽然是 this 对象的 wait() 方法,但是线程 B 进入了 waiting 状态.线程 A unlock 最后一行 notifyAll() 已经唤醒了线程 B,但是此时线程 A 还没有释放同步块的锁,线程 B 开始竞争锁对象,当线程 A 退出代码块之后,线程 B 竞争到锁执行后续操作.

有几个注意点,调用锁对象的 wait() 方法,挂起的是当前线程.用 while 而不用 if 的是防止虚假唤醒,当被虚假唤醒之后就继续往前走了而不会因为情况不对再回来 wait().

例子

其实 wait(),notify() 很直观,但是需要考虑很多情况,比如哪个线程先进来,用哪些标志来确定是否要 wait(),是需要细心考虑的方法.

前两天看到一道题目,感觉很适合结合抢占和 wait() 来讲.

三个线程,名称分别为 t1,t2t3,要求三个线程启动之后,先打印 t1,再打印 t2t3 其中一个,然后再打印 t1 如此循环.如:t1 t2 t1 t3 t1 t2 .

首先来个抢占式的

public class TestWait {

    public static void main(String[] args) throws InterruptedException {
        final singlePrint singlePrint = new singlePrint();
        new Thread(singlePrint,"t3").start();
        new Thread(singlePrint,"t1").start();
        new Thread(singlePrint,"t2").start();
        Thread.sleep(10000);
    }


}

class singlePrint implements Runnable{
    boolean flag = true;
    public  synchronized void print(Thread t) throws InterruptedException {
        if(t.getName().equals("t1")&&flag){
            System.out.println("t1");
            flag = false;
        }else if((t.getName().equals("t2")||t.getName().equals("t3"))&&!flag){
            System.out.println(t.getName());
            flag = true;
        }

    }

    @Override
    public void run() {
        while (true){
            try {
                print(Thread.currentThread());
//                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

通过 jvisualvm 来查看一下线程之间的状况

不是监视就是正在运行,三个线程之间竞争十分激烈.

来使用一下 wait()notify(),原谅我太懒了,没有把线程中的匿名内部类抽出来,因为我直接复制粘贴更快:p.

public class Test{
    
    
    public static void main(String[] args) throws InterruptedException {
        final SingleFlagLock s = new SingleFlagLock();
        new Thread(() -> {
            while (true) {
                try {
                    s.tPrint(Thread.currentThread());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t2").start();
        new Thread(() -> {
            while (true) {
                try {
                    s.tPrint(Thread.currentThread());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t3").start();

        new Thread(() -> {
            while (true) {
                try {
                    s.t1Print(Thread.currentThread());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();
        Thread.sleep(10000);
    }
}


class SingleFlagLock {
    private boolean t1Printed = false;


    public synchronized void t1Print(Thread t) throws InterruptedException {
        while (t1Printed) {
            wait();
        }
        System.out.println(t.getName());
        t1Printed = true;
        Thread.sleep(1000);
        notifyAll();
    }

    public synchronized void tPrint(Thread t) throws InterruptedException {
        while (!t1Printed) {
            wait();
        }
        System.out.println(t.getName());
        t1Printed = false;
        Thread.sleep(1000);
        notifyAll();
    }


}

再来看一下 jvisualvm 中线程之间的状态变化.

多了一个等待状态,长时间的监视红条已经不见了,线程们该吃吃该睡睡了.

到这应该对 wait()notify() 有抽象和直观的理解了.文章就到此为止了,如果三个线程按照 t1 t2 t3 t1 t2 t3 这种方式循环打印你能做出来吗:p.

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3169 引用 • 8208 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...