Java线程间通信之wait/notify

时间:2022-09-28 00:18:39

  Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式。我们来看下相关定义:

    wait() :调用该方法的线程进入WATTING状态,只有等待另外线程的通知或中断才会返回,调用wait()方法后,会释放对象的锁。

    wait(long):超时等待最多long毫秒,如果没有通知就超时返回。

    notify() : 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁。

    notifyAll():通知所有等待在该对象上的线程。

一个小例子

  我们来模拟个简单的例子来说明,我们楼下有个小小的饺子馆,生意火爆,店里有一个厨师,一个服务员,为避免厨师每做好一份,服务员端出去一份,效率太低且浪费体力。现假设厨师每做好10份,服务员就用一个大木盘子端给客户,每天卖够100份就打烊收工,厨师服务员各自回家休息。

  思考一下,要实现该功能,如果不使用等待/通知机制,那么最直接的方式可能就是,服务员隔一段时间去厨房看看,满10份就用盘子端出去。这种方式有两个很大的弊病:

  1.如果服务员去厨房看的太勤快,服务员太累了,这样还不如每做一碗就端一碗给客人,大木盘子的作用就体现不出来了。具体表现在实现代码层面就是:需要不断的循环,浪费处理器资源。

  2.如果服务员隔很久才去厨房看一下,就无法确保及时性了,可能厨师早都做够10份了,服务员却没观察到。

  针对上面这个例子,使用等待/通知机制就合理的多了,厨师每做够10份,就喊一声“饺子好了,可以端走啦”。服务员收到通知,就去厨房将饺子端给客人;厨师还没做够,即还没收到厨师的通知,就可以稍微休息下,但也得竖起耳朵等候厨师的通知。  

package ConcurrentTest;

/**
 * Created by chengxiao on 2017/6/17.
 */
public class JiaoziDemo {
    //创建个共享对象做监视器用
    private static Object obj = new Object();
    //大木盘子,一盘最多可盛10份饺子,厨师做满10份,服务员就可以端出去了。
    private static Integer platter = 0;
    //卖出的饺子总量,卖够100份就打烊收工
    private static Integer count = 0;

    /**
     * 厨师
     */
    static class Cook implements Runnable{
        @Override
        public void run() {
            while(count<100){
                synchronized (obj){
                    while (platter<10){
                        platter++;
                    }
                    //通知服务员饺子好了,可以端走了
                    obj.notify();
                    System.out.println(Thread.currentThread().getName()+"--饺子好啦,厨师休息会儿");
                }
                try {
                    //线程睡一会,帮助服务员线程抢到对象锁
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"--打烊收工,厨师回家");
        }
    }

    /**
     * 服务员
     */
    static class Waiter implements Runnable{
        @Override
        public void run() {
            while(count<100){
                synchronized (obj){
                    while(platter < 10){
                        try {
                            System.out.println(Thread.currentThread().getName()+"--饺子还没好,等待厨师通知...");                 //厨师还没做够10份,服务员等待中
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //饺子端给客人了,盘子清空
                    platter-=10;
                    //又卖出去了10份。
                    count+=10;
                    System.out.println(Thread.currentThread().getName()+"--服务员把饺子端给客人了");
                }
            }
            System.out.println(Thread.currentThread().getName()+"--打烊收工,服务员回家");

        }
    }
    public static void main(String []args){
        Thread cookThread = new Thread(new Cook(),"cookThread");
        Thread waiterThread = new Thread(new Waiter(),"waiterThread");
        cookThread.start();
        waiterThread.start();
    }
}

运行结果

cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--打烊收工,服务员回家
cookThread--打烊收工,厨师回家

执行结果

运行机制

我们来了解下wait/notify的运行机制,借用《并发编程的艺术》中的一个图

Java线程间通信之wait/notify

可能有人会对所谓监视器(monitor),对象锁(lock)不甚了解,在此简单解释下:

  jvm为每一个对象和类都关联一个锁,锁住了一个对象,就是获得了对象相关联的监视器。

  只有获取到对象锁,才能拿到监视器,如果获取锁失败了,那么线程就会进入阻塞队列中;如果成功拿到对象锁,也可以使用wait()方法,在监视器上等待,此时会释放锁,并进入等地队列中。

  关于锁和监视器的区别,园子里有个哥们的文章写得很详细透彻,在此引用一下,有兴趣的童鞋可以了解一下锁和监视器之间的区别 - Java并发

根据上面的图我们来理一下具体的过程

  1.首先,waitThread获取对象锁,然后调用wait()方法,此时,wait线程会放弃对象锁,同时进入对象的等待队列WaitQueue中。

   2.notifyThread线程抢占到对象锁,执行一些操作后,调用notify()方法,此时会将等待线程waitThread从等待队列WaitQueue中移到同步队列SynchronizedQueue中,waitThread由waitting状态变为blocked状态。需要注意的时,notifyThread此时并不会立即释放锁,它继续运行,把自己剩余的事儿干完之后才会释放锁。

  3.waitThread再次获取到对象锁,从wait()方法返回继续执行后续的操作。

  4.一个基于等待/通知机制的线程间通信的过程结束

至于notifyAll则是在第二步中将等待队列中的所有线程移到同步队列中去。

避免踩坑

  在使用wait/notify/notifyAll时有一些特别留意的,在此再总结一下:

    1.一定在synchronized中使用wait()/notify()/notifyAll(),也就是说一定要先获取锁,这个前面我们讲过,因为只有加锁后,才能获得监视器。否则jvm也会抛出IllegalMonitorStateException异常。

    2.使用wait()时,判断线程是否进入wait状态的条件一定要使用while而不要使用if,因为等待线程可能会被错误地唤醒,所以应该使用while循环在等待前等待后都检查唤醒条件是否被满足,保证安全性。

    3.notify()或notifyAll()方法调用后,线程不会立即释放锁。调用只会将wait中的线程从等待队列移到同步队列,也就是线程状态从waitting变为blocked;

    4.从wait()方法返回的前提是线程重新获得了调用对象的锁。

Java线程间通信之wait/notify的更多相关文章

  1. Java线程间通信-回调的实现方式

    Java线程间通信-回调的实现方式   Java线程间通信是非常复杂的问题的.线程间通信问题本质上是如何将与线程相关的变量或者对象传递给别的线程,从而实现交互.   比如举一个简单例子,有一个多线程的 ...

  2. Java 如何实现线程间通信?(notify、join、CountdownLatch、CyclicBarrier、FutureTask、Callable )

    转自:https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247486499&idx=1&sn=d3f2d6959df ...

  3. java线程间通信:一个小Demo完全搞懂

    版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程系列文章只是自己知识的总结梳理,都是最基础的玩意,已经掌握熟练的可以绕过. 一.从一个小Demo说起 上篇我们聊到了Java多线程的同步 ...

  4. Java——线程间通信

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

  5. 说说Java线程间通信

    序言 正文 [一] Java线程间如何通信? 线程间通信的目标是使线程间能够互相发送信号,包括如下几种方式: 1.通过共享对象通信 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值:线程A在 ...

  6. (三)&lpar;1&rpar;线程间通信---wait和notify的使用

    这篇博客记录线程间通信相关api使用以及理解. 首先第一点,我之前的博客里的线程之间也是通信的,但是他们的通信是建立在访问的是同一个变量上的,相当于是变量.数据层面上的通信,而下面要讲的是线程层面上的 ...

  7. 说说 Java 线程间通信

    序言 正文 一.Java线程间如何通信? 线程间通信的目标是使线程间能够互相发送信号,包括如下几种方式: 1.通过共享对象通信 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值:线程A在一个 ...

  8. java线程间通信1--简单实例

    线程通信 一.线程间通信的条件 1.两个以上的线程访问同一块内存 2.线程同步,关键字 synchronized 二.线程间通信主要涉及的方法 wait(); ----> 用于阻塞进程 noti ...

  9. Java并发编程&colon;线程间通信wait、notify

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

随机推荐

  1. vim颜色选择+按&lt&semi;F9&gt&semi;自动编译运行+其他基本配置&lpar;ubuntu&rpar;

    (以下是ubuntu上的配置........ 但如果你是在window上的,直接用一下配置吧(懒得介绍了)=.= syntax on filetype indent plugin on set rul ...

  2. 通过虚拟机VMware来练习安装ESXi

    关于VMware vSphere组件ESXi,大家请自行百度.大概的意思我简单的先理解为这个组件是通过在服务器上安装上ESXi系统,继而虚拟化整个服务器的硬件资源为之后虚拟各种客户端所用.相比较大家较 ...

  3. Android中解析XML的方法

    假设我要解析如下的XML文件: <?xml version="1.0" encoding="UTF-8"?> <books> <b ...

  4. ZXing工具类v1&period;0

    package com.jadyer.util; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import jav ...

  5. HTML form表单小结

    HTML form标签小结 最近研究 form标签,有一些小心得写下来与大家分享分享,共勉.在小结的最后有一个form表单的小例子,可以作为参考. -----DanlV form是HTML的一个极为重 ...

  6. aria-label

    元素中的 aria-label用来命名一个元素     它的值可以是任何字符   读屏软件就会读出aria-label里的内容 <div role=”form” aria-labelledby= ...

  7. 对于ES6中Promise的个人见解

    1.js中常见的异步 JavaScript可以响应用户的一些异步交互,比如单击鼠标和按键盘等操作. let button = document.getElementById("btn&quo ...

  8. Android相关面试题---初识

    一 .Activity的生命周期 Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称为返回栈(Back Stack).每当我们启动一个新的活动,它会 ...

  9. 【C&sol;C&plus;&plus;】C&plus;&plus;11 Move&comma; Forward

    左值与右值 Lvalue:可以出现在 operator= 左边的 Rvalue:只能出现在operator= 右边的 ; int a = b; a = b; a = a + b; a + b = a; ...

  10. spring&plus;activemq配置文件内容及实现原理

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...