用一个例子来说明Object对象中的wait方法和notifyAll方法的使用。

首先定义一个消息类,用于封装数据,以供读写线程进行操作:

 /**
* 消息
*
* @author syj
*/
public class Message { private String msg; public String getMsg() {
return msg;
} public void setMsg(String msg) {
this.msg = msg;
}
}

创建一个读线程,从Message对象中读取数据,如果没有数据,就使用 wait() 方法一直阻塞等待结果(等待后面的写线程写入数据):

 /**
* 读线程
*
* @author syj
*/
public class Reader implements Runnable { private Message message; public Reader(Message message) {
this.message = message;
} @Override
public void run() {
synchronized (message) {
try {
// 务必加上该判断,否则可能会因某个读线程在写线程的 notifyAll() 之后执行,
// 这将导致该读线程永远无法被唤醒,程序会一直被阻塞
if (message.getMsg() == null) {
message.wait();// 等待被 message.notify() 或 message.notifyAll() 唤醒
}
} catch (InterruptedException e) {
e.printStackTrace();
}
// 读取 message 对象中的数据
System.out.println(Thread.currentThread().getName() + " - " + message.getMsg());
}
}
}

创建一个写线程,往Message对象中写数据,写入成功就调用 message.notifyAll() 方法来唤醒在 message.wait() 上阻塞的线程(上面的读线程将被唤醒,读线程解除阻塞继续执行):

 import java.util.UUID;

 /**
* 写线程
*
* @author syj
*/
public class Writer implements Runnable { private Message message; public Writer(Message message) {
this.message = message;
} @Override
public void run() {
synchronized (message) {
try {
Thread.sleep(1000L);// 模拟业务耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
// 向 message 对象中写数据
message.setMsg(Thread.currentThread().getName() + ":" + UUID.randomUUID().toString().replace("-", ""));
message.notifyAll();// 唤醒所有 message.wait()
}
}
}

注意,读线程的等待和写线程的唤醒,必须调用同一个对象上的wait或notifyAll方法,并且对这两个方法的调用一定要放在synchronized块中。

这里的读线程和写线程使用的同一个对象是message,读线程调用message.wait()方法进行阻塞,写线程调用message.notifyAll()方法唤醒所有(因为调用message.wait()方法的可能会有对个线程,在本例中就有两个读线程调用了message.wait() 方法)读线程的阻塞。

写一个测试类,启动两个读线程,从Message对象中读取数据,再启动一个写线程,往Message对象中写数据:

 /**
* 测试 Object 对象中的 wait()/notifyAll() 用法
*
* @author syj
*/
public class LockApp {
public static void main(String[] args) {
Message message = new Message();
new Thread(new Reader(message), "R1").start();// 读线程 名称 R1
new Thread(new Reader(message), "R2").start();// 读线程 名称 R2
new Thread(new Writer(message), "W").start();// 写线程 名称 W
}
}

控制台打印结果:

R2 - W:4840dbd6b312489a9734414dd99a4bcb
R1 - W:4840dbd6b312489a9734414dd99a4bcb

其中R2代表第二个读线程,R2是这个读线程的名字。R1是第一个读线程,线程名叫R2。后面的uui就是模拟的异步执行结果了,W代表写线程的名字,表示数据是由写线程写入的。 由于我们只开启一个写线程,所有两条数据的uuid是同一个,只不过被两个读线程都接收到了而已。

抛出一个问题:Object对象的这个特性有什么用呢?

它比较适合用在同步等待异步处理结果的场景中。比如,在RPC框架中,Netty服务器通常返回结果是异步的,而Netty客户端想要拿到这个异步结果进行处理,该怎么做呢?

下面使用伪代码来模拟这个场景:

 import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; /**
* 使用 Object对象的 wait() 和 notifyAll() 实现同步等待异步结果
*
* @author syj
*/
public class App { // 用于存放异步结果, key是请求ID, value是异步结果
private static ConcurrentHashMap<String, String> resultMap = new ConcurrentHashMap<>();
private Object lock = new Object(); /**
* 写数据到 resultMap,写入成功唤醒所有在 lock 对象上等待的线程
*
* @param requestId
* @param message
*/
public void set(String requestId, String message) {
resultMap.put(requestId, message);
synchronized (lock) {
lock.notifyAll();
}
} /**
* 从 resultMap 中读数据,如果没有数据则等待
*
* @param requestId
* @return
*/
public String get(String requestId) {
synchronized (lock) {
try {
if (resultMap.get(requestId) == null) {
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return resultMap.get(requestId);
} /**
* 移除结果
*
* @param requestId
*/
public void remove(String requestId) {
resultMap.remove(requestId);
} /**
* 测试方法
*
* @param args
*/
public static void main(String[] args) {
// 请求唯一标识
String requestId = UUID.randomUUID().toString();
App app = new App();
try {
// 模拟Netty服务端异步返回结果
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000L);// 模拟业务耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
// 写入数据
app.set(requestId, UUID.randomUUID().toString().replace("-", ""));
}
}).start(); // 模拟Netty客户端同步等待读取Netty服务器端返回的结果
String message = app.get(requestId);
System.out.println(message);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 结果不再使用,一定要移除,以防止内容溢出
app.remove(requestId);
}
}
}

这里定义了一个静态的ConcurrentHashMap容器,来存放Netty服务器返回的异步结果,key是请求的id,value就是异步执行结果。

调用set方法可以往容器中写入数据(写入请求ID和相对应的执行结果),调用get方法可以从容器读取数据(根据请求ID获取对应的执行结果)。

get方法中调用lock对象的wait方法进行阻塞等待结果,set方法往容器中写入结果之后,紧接着调用的是同一个lock对象的notifyAll方法来唤醒该lock对象上的所有wait()阻塞线程。

以此来达到同步等待获取异步执行结果的目的。

参考文章:https://cloud.tencent.com/developer/article/1155102

Java之Object对象中的wait()和notifyAll()用法的更多相关文章

  1. Java Object对象中的wait,notify,notifyAll的理解

    wait,notify,notifyAll 是定义在Object类的实例方法,用于控制线程状态,在线程协作时,大家都会用到notify()或者notifyAll()方法,其中wait与notify是j ...

  2. Java:Object对象小记

    Java:Object对象小记 对 Java 中的 Object 对象,做一个微不足道的小小小小记 Object 的常用方法有哪些 clone() 方法:用于创建并返回当前对象的一份拷贝: 在Java ...

  3. 【java基础】java中Object对象中的Hashcode方法的作用

    以下是关于HashCode的官方文档定义: hashcode方法返回该对象的哈希码值.支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表. hashCode  ...

  4. Java的Object对象

    Object对象是除了基础对象之外,所有的对象都需要继承的父对象,包括数组也继承了Object Object里面的关键函数罗列如下: clone();调用该函数需要实现 Cloneable,否则会抛出 ...

  5. JAVA将Object对象转byte数组

    /** * 将Object对象转byte数组 * @param obj byte数组的object对象 * @return */ public static byte[] toByteArray(Ob ...

  6. 如何向java后台的对象中传数组

    1.后台对象的参数需要是是list对象 /* * copyright : GLOBALROAM Ptd Ltd * VmCreateInfo.java * Author: * zhangpengyan ...

  7. 从Object对象中读取属性的值

    C#是强类型语言,强到多变态?一个对象没有定义某个属性,你想点出来,IDE直接给你报语法错误.远不如js那么自由,想怎么点怎么点. 如果你从别人接口中拿到的就是Object类型,你想获取某个属性的值怎 ...

  8. java继承的对象中构造函数的调用顺序

    建立两个继承关系的对象 public class Machine { public String machieNameString; public Machine() { System.out.pri ...

  9. c# 如何中List<object>中去掉object对象中的重复列数据?

    //去掉重复 var title = modelList.GroupBy(m => m.Title.ToLower().Trim()).Select(m => new { ID = m.F ...

随机推荐

  1. 介绍一个二次排序的小技巧(best coder27期1001jump jump jump)

    先来描述一下问题: 问题描述 有n小孩在比赛跳远,看谁跳的最远.每个小孩可以跳3次,这个小孩的成绩就是三次距离里面的最大值.例如,一个小孩跳3次的距离分别时10, 30和20,那么这个小孩的成绩就是3 ...

  2. 如何使用Arduino和SIM900A GPRS / GSM模块将数据发送到Web服务器

    今天我们在这里介绍一个非常有趣的项目,我们将使用Arduino开发板和GPRS将数据发送到SparkFun服务器.这是一个基于IoT的项目,我们将使用GSM模块SIM900A将一些数据发送到互联网上的 ...

  3. LG4718 【模板】Pollard-Rho算法 和 [Cqoi2016]密钥破解

    Pollard-Rho算法 总结了各种卡常技巧的代码: #define int long long typedef __int128 LL; IN int fpow(int a,int b,int m ...

  4. python数据分析之数据分布

    转自链接:https://blog.csdn.net/YEPAO01/article/details/99197487 一.查看数据分布趋势 import pandas as pd import nu ...

  5. 题解 UVa11889

    题目大意 \(T\) 组数据,每组数据给定两个正整数 \(A,C\),求使 \(LCM(A,B)=C\) 的最小的 \(B\),若无解则输出NO SOLUTION. 分析 当 \(C\%A=0\) 时 ...

  6. Kafka+kylin——kylin2.5.0流式构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/a_drjiaoda/article/d ...

  7. mage Ansible学习3 ansible role实例

    一.ansible配置文件解析 1./etc/ansible/ansible.cfg配置文件详解 [root@node3 ~]# cat /etc/ansible/ansible.cfg |grep ...

  8. Kubernetes 学习11 kubernetes ingress及ingress controller

    一.上集回顾 1.Service 3种模型:userspace,iptables,ipvs 2.Service类型 ClusterIP,NodePort NodePort:client -> N ...

  9. 【转发】c#做端口转发程序支持正向连接和反向链接

    可以通过中转server来连接sql server,连接的时候用ip,port,不是冒号,是逗号 但试过local port 21想连接AS400的FTP却不成功...为咩涅... https://w ...

  10. 笔记-读官方Git教程(1)~认识Git

    小书匠版本管理 教程内容基本来自git官方教程,认真都了系列的文章,然后对一些重点的记录下来,做了简单的归纳并写上自己的思考. 目录: 1.Git介绍 2.Git版本控制原理 3.Git特点 4.Gi ...