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 ...
随机推荐
- C++ --- 编码习惯小区别
定义一个变量,最好养成C代码的习惯 C代码: 用到的变量X,要在最开始定义:先定义,用的时候在初始化: [例如] int x; //先定义 ..... ..... ; //再初始化 C++代码: 用到 ...
- 用python爬取全网妹子图片【附源码笔记】
这是晚上没事无聊写的python爬虫小程序,专门爬取妹子图的,养眼用的,嘻嘻!身为程序狗只会这个了! 废话不多说,代码附上,仅供参考学习! """ 功能:爬取妹子图全网妹 ...
- HDU2870 Largest Submatrix
Largest Submatrix Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others ...
- Oracle 查询结果去重保留一项
首先因为需要查询很多字段,也就排除了使用distinct的可能性. 1.1 原始sql select finalSql.* from (select '' SMS_CONTENT, ' as 短信发出 ...
- drf框架 - 请求模块 | 渲染模块
Postman接口工具 官方 https://www.getpostman.com/ get请求,携带参数采用Paramspost等请求,提交数据包可以采用三种方式:form-date.urlenc ...
- 多线程实现的方式一继承Thread
实现方法一:继承Thread类 package thread; /** * @function 多线程继承Thread类 * @author hj */ public class Threads ex ...
- Django源码分析rest_framework 关于re_path('^publish/', views.PublishView.as_view()总结
1. ApiView 定义一个cbc视图 class BookView (APIView):pass re_path(r"books/$", views.BookView.as_v ...
- Kubernetes 学习3 kubeadm初始化k8s集群
一.k8s集群 1.k8s整体架构图 2.k8s网络架构图 二.基于kubeadm安装k8s步骤 1.master,nodes:安装kubelet,kubeadm,docker 2.master: k ...
- fedora安装设置
添加视频解码rpmfusion源: sudo rpm -ivh http://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-st ...
- mac系统提示 interactive intelligence 的恼人问题
处理 interacti intelligence 提示问题记录 二手购买了一台电脑,从最初的小白到现在稍微熟悉mac的使用, 一直困扰我的便是一个提示, 上图 困扰多年, 记录一下解决和尝试过程吧. ...