2020-12-08

被线程坑惨了!java中的wait、notify、notifyAll详解

在java中,线程间的通信可以使用wait、notify、notifyAll来进行控制。从名字就可以看出来这3个方法都是跟多线程相关的,但是可能让你感到吃惊的是:这3个方法并不是Thread类或者是Runnable接口的方法,而是Object类的3个本地方法。
在这里插入图片描述

下图是我总结的Java资料,想要资料的话请扫描下方二维码。

在这里插入图片描述

其实要理解这一点也并不难,调用一个Object的wait与notify/notifyAll的时候,必须保证调用代码对该Object是同步的,也就是说必须在作用等同于synchronized(obj){…}的内部才能够去调用obj的wait与notify/notifyAll三个方法,否则就会报错:

  java.lang.IllegalMonitorStateException:current thread not owner
  • 1

也就是说,在调用这3个方法的时候,当前线程必须获得这个对象的锁,那么这3个方法就是和对象锁相关的,所以是属于Object的方法而不是Thread,因为不是每个对象都是Thread。所以我们在理解wait、notify、notifyAll之前,先要了解以下对象锁。

多个线程都持有同一个对象的时候,如果都要进入synchronized(obj){…}的内部,就必须拿到这个对象的锁,synchronized的机制保证了同一时间最多只能有1个线程拿到了对象的锁,如下图:

在这里插入图片描述

下面我们来看一下这3个方法的作用:

  • wait:线程自动释放其占有的对象锁,并等待notify
  • notify:唤醒一个正在wait当前对象锁的线程,并让它拿到对象锁
  • notifyAll:唤醒所有正在wait前对象锁的线程

notify和notifyAll的最主要的区别是:notify只是唤醒一个正在wait当前对象锁的线程,而notifyAll唤醒所有。值得注意的是:notify是本地方法,具体唤醒哪一个线程由虚拟机控制;notifyAll后并不是所有的线程都能马上往下执行,它们只是跳出了wait状态,接下来它们还会是竞争对象锁。

下面通过一个常用生产者、消费者的例子来说明。
消息实体类:

 
  1. package com.podongfeng;

  2.  
  3. /**

  4. * Title: Message.class<br>

  5. * Description: 消息实体<br>

  6. * Create DateTime: 2016年04月17日 下午1:27 <br>

  7. *

  8. * @author podongfeng

  9. */

  10. public class Message {

  11. }

生产者:

 
  1. package com.podongfeng;

  2.  
  3. import java.util.ArrayList;

  4. import java.util.List;

  5.  
  6. /**

  7. * Title: Producer.class<br>

  8. * Description: 消息生产者<br>

  9. * Create DateTime: 2016年04月17日 下午1:28 <br>

  10. *

  11. * @author podongfeng

  12. */

  13. public class Producer extends Thread {

  14.  
  15. List<Message> msgList = new ArrayList<>();

  16.  
  17. @Override public void run() {

  18. try {

  19. while (true) {

  20. Thread.sleep(3000);

  21. Message msg = new Message();

  22. synchronized(msgList) {

  23. msgList.add(msg);

  24. msgList.notify(); //这里只能是notify而不能是notifyAll,否则remove(0)会报java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

  25. }

  26. }

  27. } catch (Exception e) {

  28. e.printStackTrace();

  29. }

  30. }

  31.  
  32. public Message waitMsg() {

  33. synchronized(msgList) {

  34. if(msgList.size() == 0) {

  35. try {

  36. msgList.wait();

  37. } catch(InterruptedException e) {

  38. e.printStackTrace();

  39. }

  40. }

  41. return msgList.remove(0);

  42. }

  43. }

  44. }

  45.  

消费者:

 
  1. package com.podongfeng;

  2.  
  3. /**

  4. * Title: Consumer.class<br>

  5. * Description: 消息消费者<br>

  6. * Create DateTime: 2016年04月17日 下午1:28 <br>

  7. *

  8. * @author podongfeng

  9. */

  10. public class Consumer extends Thread {

  11.  
  12. private Producer producer;

  13.  
  14. public Consumer(String name, Producer producer) {

  15. super(name);

  16. this.producer = producer;

  17. }

  18.  
  19. @Override public void run() {

  20. while (true) {

  21. Message msg = producer.waitMsg();

  22. System.out.println("Consumer " + getName() + " get a msg");

  23. }

  24. }

  25.  
  26. public static void main(String[] args) {

  27. Producer p = new Producer();

  28. p.start();

  29. new Consumer("Consumer1", p).start();

  30. new Consumer("Consumer2", p).start();

  31. new Consumer("Consumer3", p).start();

  32. }

  33. }

  34.  
  •  

消费者线程调用waitMsg去获取一个消息实体,如果msgList为空,则线程进入wait状态;生产这线程每隔3秒钟生产出体格msg实体并放入msgList列表,完成后,调用notify唤醒一个消费者线程去消费。

最后再次提醒注意:
wait、notify、notifyAll并不是Thread类或者是Runnable接口的方法,而是Object类的3个本地方法。
在调用这3个方法的时候,当前线程必须获得这个对象的锁

最后的惯例:点个小赞,好运不断,来个关注,青春常驻,打个小赏,工资疯涨。。。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值