java object.wait_Java之Object对象中的wait()和notifyAll()用法

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

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

1 /**

2 * 消息3 *4 *@authorsyj5 */

6 public classMessage {7

8 privateString msg;9

10 publicString getMsg() {11 returnmsg;12 }13

14 public voidsetMsg(String msg) {15 this.msg =msg;16 }17 }

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

1 /**

2 * 读线程3 *4 *@authorsyj5 */

6 public class Reader implementsRunnable {7

8 privateMessage message;9

10 publicReader(Message message) {11 this.message =message;12 }13

14 @Override15 public voidrun() {16 synchronized(message) {17 try{18 //务必加上该判断,否则可能会因某个读线程在写线程的 notifyAll() 之后执行,19 //这将导致该读线程永远无法被唤醒,程序会一直被阻塞

20 if (message.getMsg() == null) {21 message.wait();//等待被 message.notify() 或 message.notifyAll() 唤醒

22 }23 } catch(InterruptedException e) {24 e.printStackTrace();25 }26 //读取 message 对象中的数据

27 System.out.println(Thread.currentThread().getName() + " - " +message.getMsg());28 }29 }30 }

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

1 importjava.util.UUID;2

3 /**

4 * 写线程5 *6 *@authorsyj7 */

8 public class Writer implementsRunnable {9

10 privateMessage message;11

12 publicWriter(Message message) {13 this.message =message;14 }15

16 @Override17 public voidrun() {18 synchronized(message) {19 try{20 Thread.sleep(1000L);//模拟业务耗时

21 } catch(InterruptedException e) {22 e.printStackTrace();23 }24 //向 message 对象中写数据

25 message.setMsg(Thread.currentThread().getName() + ":" + UUID.randomUUID().toString().replace("-", ""));26 message.notifyAll();//唤醒所有 message.wait()

27 }28 }29 }

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

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

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

1 /**

2 * 测试 Object 对象中的 wait()/notifyAll() 用法3 *4 *@authorsyj5 */

6 public classLockApp {7 public static voidmain(String[] args) {8 Message message = newMessage();9 new Thread(new Reader(message), "R1").start();//读线程 名称 R1

10 new Thread(new Reader(message), "R2").start();//读线程 名称 R2

11 new Thread(new Writer(message), "W").start();//写线程 名称 W

12 }13 }

控制台打印结果:

R2 -W:4840dbd6b312489a9734414dd99a4bcb

R1- W:4840dbd6b312489a9734414dd99a4bcb

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

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

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

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

1 importjava.util.UUID;2 importjava.util.concurrent.ConcurrentHashMap;3

4 /**

5 * 使用 Object对象的 wait() 和 notifyAll() 实现同步等待异步结果6 *7 *@authorsyj8 */

9 public classApp {10

11 //用于存放异步结果, key是请求ID, value是异步结果

12 private static ConcurrentHashMap resultMap = new ConcurrentHashMap<>();13 private Object lock = newObject();14

15 /**

16 * 写数据到 resultMap,写入成功唤醒所有在 lock 对象上等待的线程17 *18 *@paramrequestId19 *@parammessage20 */

21 public voidset(String requestId, String message) {22 resultMap.put(requestId, message);23 synchronized(lock) {24 lock.notifyAll();25 }26 }27

28 /**

29 * 从 resultMap 中读数据,如果没有数据则等待30 *31 *@paramrequestId32 *@return

33 */

34 publicString get(String requestId) {35 synchronized(lock) {36 try{37 if (resultMap.get(requestId) == null) {38 lock.wait();39 }40 } catch(InterruptedException e) {41 e.printStackTrace();42 }43 }44 returnresultMap.get(requestId);45 }46

47 /**

48 * 移除结果49 *50 *@paramrequestId51 */

52 public voidremove(String requestId) {53 resultMap.remove(requestId);54 }55

56 /**

57 * 测试方法58 *59 *@paramargs60 */

61 public static voidmain(String[] args) {62 //请求唯一标识

63 String requestId =UUID.randomUUID().toString();64 App app = newApp();65 try{66 //模拟Netty服务端异步返回结果

67 new Thread(newRunnable() {68 @Override69 public voidrun() {70 try{71 Thread.sleep(2000L);//模拟业务耗时

72 } catch(InterruptedException e) {73 e.printStackTrace();74 }75 //写入数据

76 app.set(requestId, UUID.randomUUID().toString().replace("-", ""));77 }78 }).start();79

80 //模拟Netty客户端同步等待读取Netty服务器端返回的结果

81 String message =app.get(requestId);82 System.out.println(message);83 } catch(Exception e) {84 e.printStackTrace();85 } finally{86 //结果不再使用,一定要移除,以防止内容溢出

87 app.remove(requestId);88 }89 }90 }

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值