首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

线程之间的协作(等待通知模式)

等待和通知

  等待和通知的标准范式

    等待方:

      1:获取对象的锁

      2:在循环中判断条件是否满足,不满足调用wait方法继续阻塞,为啥要要循环中判断呢?因为该线程被唤醒之后可能条件依旧不满足

      3:条件满足,执行业务逻辑

    通知方:

      1:获取对象的锁

      2:改变相关条件

      3:通知所有等待在对象的线程

都是属于Object的方法

等待:wait

通知:notify/notifyAll

需求:一个快递在变更;里程数和地点的时候通知等待的线程处理变更后的请求

测试使用notifyAll唤醒

实体类

代码语言:javascript
复制
package org.dance.day1.wn;

/**
 * 快递实体类
 *
 * @author ZYGisComputer
 */
public class Express {

    public final static String CITY = "ShangHai";

    /**
     * 快递运输的里程数
     */
    private int km;

    /**
     * 快递到达的地点
     */
    private String site;

    public Express() {
    }

    public Express(int km, String site) {
        this.km = km;
        this.site = site;
    }

    /**
     * 变化公里数:然后通知处于wait状态并需要处理公里数的线程进行业务处理
     */
    public synchronized void checkKm() {
//      变化公里数
        this.km = 101;
//      全部通知
        notifyAll();
    }

    /**
     * 变化地点:然后通知处于wait状态并需要处理地点的线程进行业务处理
     */
    public synchronized void checkSite() {
//        变化城市
        this.site = "BeiJin";
//         全部通知
        notifyAll();
    }

    public synchronized void waitKm() {
        // 循环等待
        while (this.km <= 100) {
            try {
                wait();
                System.out.println("check km " + Thread.currentThread().getId());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("the km is " + this.km + ", I will change DB.");
    }

    public synchronized void waitSite() {
        // 循环等待
        while (CITY.equals(this.site)) {
            try {
                wait();
                System.out.println("check site " + Thread.currentThread().getId());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("the site is " + this.site + ", I will change DB.");
    }
}

测试类

代码语言:javascript
复制
package org.dance.day1.wn;

import org.dance.tools.SleepTools;

/**
 * 测试wait notify/notifyAll
 * @author ZYGisComputer
 */
public class TestWN {

    private static Express express = new Express(0,Express.CITY);

    /**
     * 检查里程数变化的线程,不满足一直等待
     */
    private static class CheckKm extends Thread{
        @Override
        public void run() {
            express.waitKm();
        }
    }

    /**
     * 检查城市变化的线程,不满足一直等待
     */
    private static class CheckSite extends Thread{
        @Override
        public void run() {
            express.waitSite();
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new CheckKm().start();
        }
        for (int i = 0; i < 3; i++) {
            new CheckSite().start();
        }
        SleepTools.second(1);
        // 修改里程数
        express.checkKm();
    }
}

 测试结果:

代码语言:javascript
复制
check site 16
check site 15
check site 14
check km 13
the km is 101, I will change DB.
check km 12
the km is 101, I will change DB.
check km 11
the km is 101, I will change DB.

测试发现全部的线程全部被唤醒了,然后其中三个等待城市变化的线程再次进入阻塞,另外三个等待里程数变化的执行成功退出阻塞

测试使用notify唤醒

返回结果:

代码语言:javascript
复制
check km 11
the km is 101, I will change DB.
代码语言:javascript
复制
check site 11

因为notify通知任意一个在这个对象上阻塞的线程,如果正好通知到了,等待里程数的,那么也只有一个被唤醒,其他两个继续阻塞,如果通知到了一个等待城市变化的那么这个线程将继续进入阻塞,所以看来,我们应该尽量使用notifyAll少用notify,因为可能发生信号丢失的情况

代码语言:javascript
复制
/**
     * Wakes up a single thread that is waiting on this object's
     * monitor. If any threads are waiting on this object, one of them
     * is chosen to be awakened. The choice is arbitrary and occurs at
     * the discretion of the implementation. A thread waits on an object's
     * monitor by calling one of the {@code wait} methods.
     * <p>
     * The awakened thread will not be able to proceed until the current
     * thread relinquishes the lock on this object. The awakened thread will
     * compete in the usual manner with any other threads that might be
     * actively competing to synchronize on this object; for example, the
     * awakened thread enjoys no reliable privilege or disadvantage in being
     * the next thread to lock this object.
     * <p>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. A thread becomes the owner of the
     * object's monitor in one of three ways:
     * <ul>
     * <li>By executing a synchronized instance method of that object.
     * <li>By executing the body of a {@code synchronized} statement
     *     that synchronizes on the object.
     * <li>For objects of type {@code Class,} by executing a
     *     synchronized static method of that class.
     * </ul>
     * <p>
     * Only one thread at a time can own an object's monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @see        java.lang.Object#notifyAll()
     * @see        java.lang.Object#wait()
     */
    public final native void notify();

在源码中可以看到,这个方法是一个 native的

在他的描述中有一段

代码语言:javascript
复制
The choice is arbitrary and occurs at,the discretion of the implementation.

翻译为中文

代码语言:javascript
复制
选择是任意的,发生在,执行的自由裁量权。

所以说notify的唤醒是随意的,并且信号只发出一次

但是据有人说,在线程进入等待的时候会进入一个等待队列,notify会唤醒第一个等待的线程

经过在百度上一顿搜索,浏览了大量的文章之后

我得到的结果就是在HotSpot虚拟机当中 notify唤醒的是阻塞线程队列当中的第一个ObjectWaiter节点,其他虚拟机不一定.

我觉得这个问题也已当做一个在面试的时候,你问面试官的一个技术性问题

作者:彼岸舞

时间:2020\09\16

内容关于:并发编程

本文来源于网络,只做技术分享,一概不负任何责任

下一篇
举报
领券