为什么要有 wait 和 notify

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

除了 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 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3201 引用 • 8216 回帖 • 2 关注

相关帖子

欢迎来到这里!

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

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