Java之Object对象中的wait()和notifyAll()用法
用一个例子来说明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()用法的更多相关文章
- Java Object对象中的wait,notify,notifyAll的理解
wait,notify,notifyAll 是定义在Object类的实例方法,用于控制线程状态,在线程协作时,大家都会用到notify()或者notifyAll()方法,其中wait与notify是j ...
- Java:Object对象小记
Java:Object对象小记 对 Java 中的 Object 对象,做一个微不足道的小小小小记 Object 的常用方法有哪些 clone() 方法:用于创建并返回当前对象的一份拷贝: 在Java ...
- 【java基础】java中Object对象中的Hashcode方法的作用
以下是关于HashCode的官方文档定义: hashcode方法返回该对象的哈希码值.支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表. hashCode ...
- Java的Object对象
Object对象是除了基础对象之外,所有的对象都需要继承的父对象,包括数组也继承了Object Object里面的关键函数罗列如下: clone();调用该函数需要实现 Cloneable,否则会抛出 ...
- JAVA将Object对象转byte数组
/** * 将Object对象转byte数组 * @param obj byte数组的object对象 * @return */ public static byte[] toByteArray(Ob ...
- 如何向java后台的对象中传数组
1.后台对象的参数需要是是list对象 /* * copyright : GLOBALROAM Ptd Ltd * VmCreateInfo.java * Author: * zhangpengyan ...
- 从Object对象中读取属性的值
C#是强类型语言,强到多变态?一个对象没有定义某个属性,你想点出来,IDE直接给你报语法错误.远不如js那么自由,想怎么点怎么点. 如果你从别人接口中拿到的就是Object类型,你想获取某个属性的值怎 ...
- java继承的对象中构造函数的调用顺序
建立两个继承关系的对象 public class Machine { public String machieNameString; public Machine() { System.out.pri ...
- c# 如何中List<object>中去掉object对象中的重复列数据?
//去掉重复 var title = modelList.GroupBy(m => m.Title.ToLower().Trim()).Select(m => new { ID = m.F ...
随机推荐
- 如何使用Arduino和SIM900A GPRS / GSM模块将数据发送到Web服务器
今天我们在这里介绍一个非常有趣的项目,我们将使用Arduino开发板和GPRS将数据发送到SparkFun服务器.这是一个基于IoT的项目,我们将使用GSM模块SIM900A将一些数据发送到互联网上的 ...
- Java精通并发-锁粗化与锁消除技术实例演示与分析
在上一次https://www.cnblogs.com/webor2006/p/11446473.html中对锁的升级进行了一个比较详细的理论化的学习,先回忆一下: 编译器对于锁的优化措施: 锁消除技 ...
- js计算两个时间差
时间格式 time:'2018-04-26 15:49:00'需要转换为time:'2018/04/26 15:49:00' 使用time.replace(/\-/g, "/") ...
- info命令
info命令也可以查看命令的信息,但是和man命令不同,info命令如同一本书,一个命令只不过是书中的一个章节. 1.基本结构: 输入:info ls ls命令只不过是整个文档的10.1章节. 而在章 ...
- ES特点
ES Hadoop spark的区别存(可扩展) hdfs存(可扩展) 不存 ...
- 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 ...
- 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 ...
- Linux下的find命令详解
0x01 简介 find命令用来在指定目录下查找文件.任何位于参数之前的字符串都将被视为欲查找的目录名.如果使用该命令时,不设置任何参数,则find命令将在当前目录下查找子目录与文件.并且将查找到的子 ...
- Vue编程基础
一.依赖环境搭建: 添加镜像 # 安装好node.js后,使用淘宝镜像 npm install -g cnpm --registry=https://registry.npm.taobao.org 项 ...
- listview1 保存和读取 listViewItems保存为txt
/* * 保存原理 * 将LISTVIEW视为一行一行的字符串 * 将所有的行合并成一个字符串 然后保存为TXT文件 ...