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 ...
随机推荐
- 非正常卸载Chrome浏览器导致无法重新安装
昨晚在使用selenium的时候,Chrome浏览器和Chromedriver.exe不兼容,前几天使用的时候都没问题,查看浏览器版本后发现浏览器已经自动升级为67版本(我之前安装的是61版本). 于 ...
- CF1167E. Range Deleting
题意 给定长度为\(n\)的数组\(a\),其中任意\(a_i \leq x\) 定义\(f(l,r)\)为删除\(a\)中值域在\([l,r]\)的数后剩余的数组. 统计满足\(1\leq l \l ...
- node gyp编译所需要的环境
安装ms的build工具包,自带python npm install --global --production windows-build-tools
- vue中url带有#号键,去除方法
在写vue项目中,发现路由跳转总是带有#,在获取数据中带来不必要的麻烦,如果我们不希望 路由中出现 # ,那怎么办呢? 解决办法: 在router ---->index 中 添加代码 mod ...
- java集合Map
参考文章:https://blog.csdn.net/yjn1995/article/details/89784891 1.map接口 1.map接口实现类,HashMap.LinkListMap.H ...
- django 第四天模板渲染
今日内容 一.模板渲染 语法 {{ 变量 }} {% 逻辑 %} 1.变量 取列表中的第几个元素,用索引 <p>{{ namelist.2 }}</p> 取字典中的第几个元素用 ...
- jsp+ ueditor word粘贴上传
最近公司做项目需要实现一个功能,在网页富文本编辑器中实现粘贴Word图文的功能. 我们在网站中使用的Web编辑器比较多,都是根据用户需求来选择的.目前还没有固定哪一个编辑器 有时候用的是UEditor ...
- 如何在Processing中用鼠标获取RGB颜色数值
要做一个抠图应用,所以随手做了个鼠标取色,代码如下: void mousePressed(){ int imgC = get(mouseX,mouseY); int R = (imgC >> ...
- ArcGIS分支版本化( Branch Versioning )技术介绍
概述 分支版本化技术是有别于传统的SDE版本化技术,它用于支持WebGIS模式下的多用户长事务编辑. 优势功能 使用分支版本化技术将获得以下功能 1. 支持长事务的编辑. 2. 支持Undo和Redo ...
- (26)打鸡儿教你Vue.js
weex框架的使用 1.weex开发入门 2.weex开发环境搭建 3.掌握部分weex组件模块 4.了解一些vue基本常见语法 5.制作一个接近原生应用体验的app weex介绍 安装开发环境 We ...