用一个例子来说明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. 如何使用Arduino和SIM900A GPRS / GSM模块将数据发送到Web服务器

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

  2. Java精通并发-锁粗化与锁消除技术实例演示与分析

    在上一次https://www.cnblogs.com/webor2006/p/11446473.html中对锁的升级进行了一个比较详细的理论化的学习,先回忆一下: 编译器对于锁的优化措施: 锁消除技 ...

  3. js计算两个时间差

    时间格式 time:'2018-04-26 15:49:00'需要转换为time:'2018/04/26 15:49:00' 使用time.replace(/\-/g, "/") ...

  4. info命令

    info命令也可以查看命令的信息,但是和man命令不同,info命令如同一本书,一个命令只不过是书中的一个章节. 1.基本结构: 输入:info ls ls命令只不过是整个文档的10.1章节. 而在章 ...

  5. ES特点

    ES                     Hadoop                       spark的区别存(可扩展)         hdfs存(可扩展)             不存 ...

  6. leetcode解题报告(31):Kth Largest Element in an Array

    描述 Find the kth largest element in an unsorted array. Note that it is the kth largest element in the ...

  7. AtCoder Grand Contest 005题解

    传送门 \(A\) 咕咕 const int N=5e5+5; char s[N];int res,n,sum; int main(){ scanf("%s",s+1),res=n ...

  8. Linux下的find命令详解

    0x01 简介 find命令用来在指定目录下查找文件.任何位于参数之前的字符串都将被视为欲查找的目录名.如果使用该命令时,不设置任何参数,则find命令将在当前目录下查找子目录与文件.并且将查找到的子 ...

  9. Vue编程基础

    一.依赖环境搭建: 添加镜像 # 安装好node.js后,使用淘宝镜像 npm install -g cnpm --registry=https://registry.npm.taobao.org 项 ...

  10. listview1 保存和读取 listViewItems保存为txt

       /*          *   保存原理          *   将LISTVIEW视为一行一行的字符串          *   将所有的行合并成一个字符串 然后保存为TXT文件       ...