zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java之Object对象中的wait()和notifyAll()用法

JAVA对象 用法 object WAIT notifyAll
2023-09-27 14:25:06 时间

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

 

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

 1 /**
 2  * 消息
 3  *
 4  * @author syj
 5  */
 6 public class Message {
 7 
 8     private String msg;
 9 
10     public String getMsg() {
11         return msg;
12     }
13 
14     public void setMsg(String msg) {
15         this.msg = msg;
16     }
17 }

 

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

 1 /**
 2  * 读线程
 3  *
 4  * @author syj
 5  */
 6 public class Reader implements Runnable {
 7 
 8     private Message message;
 9 
10     public Reader(Message message) {
11         this.message = message;
12     }
13 
14     @Override
15     public void run() {
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 import java.util.UUID;
 2 
 3 /**
 4  * 写线程
 5  *
 6  * @author syj
 7  */
 8 public class Writer implements Runnable {
 9 
10     private Message message;
11 
12     public Writer(Message message) {
13         this.message = message;
14     }
15 
16     @Override
17     public void run() {
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  * @author syj
 5  */
 6 public class LockApp {
 7     public static void main(String[] args) {
 8         Message message = new Message();
 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 import java.util.UUID;
 2 import java.util.concurrent.ConcurrentHashMap;
 3 
 4 /**
 5  * 使用 Object对象的 wait() 和 notifyAll() 实现同步等待异步结果
 6  *
 7  * @author syj
 8  */
 9 public class App {
10 
11     // 用于存放异步结果, key是请求ID, value是异步结果
12     private static ConcurrentHashMap<String, String> resultMap = new ConcurrentHashMap<>();
13     private Object lock = new Object();
14 
15     /**
16      * 写数据到 resultMap,写入成功唤醒所有在 lock 对象上等待的线程
17      *
18      * @param requestId
19      * @param message
20      */
21     public void set(String requestId, String message) {
22         resultMap.put(requestId, message);
23         synchronized (lock) {
24             lock.notifyAll();
25         }
26     }
27 
28     /**
29      * 从 resultMap 中读数据,如果没有数据则等待
30      *
31      * @param requestId
32      * @return
33      */
34     public String 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         return resultMap.get(requestId);
45     }
46 
47     /**
48      * 移除结果
49      *
50      * @param requestId
51      */
52     public void remove(String requestId) {
53         resultMap.remove(requestId);
54     }
55 
56     /**
57      * 测试方法
58      *
59      * @param args
60      */
61     public static void main(String[] args) {
62         // 请求唯一标识
63         String requestId = UUID.randomUUID().toString();
64         App app = new App();
65         try {
66             // 模拟Netty服务端异步返回结果
67             new Thread(new Runnable() {
68                 @Override
69                 public void run() {
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()阻塞线程。

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

 

 

参考文章:https://cloud.tencent.com/developer/article/1155102