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


    用一个例子来说明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

  • 相关阅读:
    8.Eclipse中创建Maven Web项目
    spin_lock &amp; mutex_lock的差别?
    如花搞笑图片集锦(转贴)
    二分查找
    WebStorm 7.0 注冊码
    Sphinx/Coreseek 4.1 跑 buildconf.sh 一个错误,无法生成configure档
    可变长度结构
    于linux已安装moodle
    采用WindowManager添加您自己的自定义视图
    mysql1130远程连接没有权限解决方法
  • 原文地址:https://www.cnblogs.com/jun1019/p/10965133.html
Copyright © 2020-2023  润新知