美文网首页
JAVA 多线程与高并发学习笔记(七)——线程间通信

JAVA 多线程与高并发学习笔记(七)——线程间通信

作者: 简单一点点 | 来源:发表于2022-07-12 22:38 被阅读0次

    如果需要多个线程按照指定的规则共同完成一个任务,那么这些线程之间需要互相协调,这个过程被称为线程间通信。

    等待-通知方式

    线程间通信有多种方式,等待-通知、共享内存、管道流,本部分主要介绍等待-通知方式。

    wait 方法和 notify 方法原理

    Java 语言中的“等待-通知”方式的线程间通信使用了对象的 wait()notify() 两类方法。这两个方法和监视器紧密相关,每个 Java 对象都有这两类实例方法。

    对象的 wait 方法

    对象的 wait 方法的主要作用是让当前线程阻塞并等待被唤醒。使用wait方法一定要放在同步块中,调用方法如下:

    sychronized(locko) {
        locko.wait();
        ...
    }
    

    Object 类中的 wait 方法有以下3个版本:

    void wait();
    
    // 限时等待
    void wait(long timeout);
    
    // 更加精确的限时等待
    void wait(long timeout, int nanos);
    

    wait 方法的核心原理大致如下:

    1. 当线程调用了某个同步锁对象的 wait() 方法后,JVM 会将当前线程加入到监视器的 WaitSet(等待集),等待被其他线程唤醒。
    2. 当前线程会释放对象监视器的 Owner 权利,让其他线程可以抢夺对象监视器。
    3. 让当前线程等待,其状态变成WAITING。

    对象的 notify 方法

    对象的 notify 方法的主要作用是唤醒在等待的线程,。使用notify方法一定要放在同步块中,调用方法如下:

    sychronized(locko) {
        locko.notify();
        ...
    }
    

    notify 方法有两个版本:

    // 唤醒监视器等待集里面的第一条等待线程,被唤醒的线程状态由WAITING变为BLOCKED。
    void notify();
    
    // h唤醒监视器等待集里的所有等待线程,被唤醒的线程状态由WAITING变为BLOCKED。
    void notifyAll();
    

    notify 方法的核心原理如下:

    1. 当线程调用了某个同步锁对象的 notify 方法或 notifyAll 方法后,JVM会唤醒监视器WaitSet中对应的线程。
    2. 等待线程被唤醒后,会从监视器的 WaitSet 移动到 EntryList,线程具备了排队抢夺监视器 Owner 权利的资格,其状态从 WAITING 变为 BLOCKED。
    3. EntryList 中的线程抢夺到监视器的 Owner 权利之后,线程的状态从 BLOCKED 变成 Runnable,具备重新执行的资格。

    实战案例

    下面看个实战案例。

    public class WaitNotifyDemo {
    
        static Object locko = new Object();
    
        static class WaitTarget implements Runnable {
            public void run() {
                synchronized (locko) {
                    try {
                        System.out.println("启动等待");
                        // 等待被通知
                        locko.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("接收到通知,当前线程继续执行");
                }
            }
        }
    
        static class NotifyTarget implements Runnable {
            public void run() {
                synchronized (locko) {
                    try {
                        // 从屏幕读取一个字符输入,目的是阻塞通知线程
                        System.out.println(System.in.read());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    locko.notifyAll();
                    System.out.println("发出通知了,但是线程还没有立刻释放锁");
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            // 创建等待线程
            Thread waitThread = new Thread(new WaitTarget(), "WaitThread");
            waitThread.start();
            Thread.sleep(1000);
            // 创建通知线程
            Thread notifyThread = new Thread(new NotifyTarget(), "NotifyThread");
            notifyThread.start();
        }
    
    }
    

    运行程序,这时候在命令行输入 jps 查看进程ID。

    >jps
    15040 Jps
    21864 WaitNotifyDemo
    22072 Launcher
    24716
    

    使用 jstack 查看进程信息。

     >jstack 21864
    2022-07-12 22:26:57
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.60-b23 mixed mode):
    
    "DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x000000000198e000 nid=0x3df4 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "NotifyThread" #13 prio=5 os_prio=0 tid=0x000000001eda6800 nid=0x5228 runnable [0x0000000020a1f000]
       java.lang.Thread.State: RUNNABLE
            at java.io.FileInputStream.readBytes(Native Method)
            at java.io.FileInputStream.read(FileInputStream.java:255)
            at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
            at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
            - locked <0x000000076bddaea8> (a java.io.BufferedInputStream)
            at com.wyk.threaddemo.WaitNotifyDemo$NotifyTarget.run(WaitNotifyDemo.java:29)
            - locked <0x000000076c17ea00> (a java.lang.Object)
            at java.lang.Thread.run(Thread.java:745)
    
    "WaitThread" #12 prio=5 os_prio=0 tid=0x000000001ed96000 nid=0x515c in Object.wait() [0x000000002091e000]
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x000000076c17ea00> (a java.lang.Object)
            at java.lang.Object.wait(Object.java:502)
            at com.wyk.threaddemo.WaitNotifyDemo$WaitTarget.run(WaitNotifyDemo.java:15)
            - locked <0x000000076c17ea00> (a java.lang.Object)
            at java.lang.Thread.run(Thread.java:745)
    
    ...
    

    可以看到 WaitThread 处于 WAITING 状态,因为它处于对象监视器的等待集中,等待被唤醒。 NotifyThread 处于 RUNNABLE 状态。

    在控制台输入任意的字符即可使代码继续执行下去。

    通过用例可知,WaitThread 调用 locko.wait 后会一直处于 WAITING 状态,不会再占用CPU时间片,也不会占用同步对象 locko 的监视器,一直到其他线程使用 locko.notify 方法发出通知。

    sychronized 同步块内部使用wait和notify

    在调用同步对象的 waitnotify 方法时,“当前线程”必须拥有该线程的同步锁,也就是必须在同步块中使用。否则JVM会抛出 java.lang.IllegalMonitorException 异常。

    这两个方法的通信要点是:

    1. 调用某个同步对象的 waitnotify 方法前,必须要取得这个锁对象的监视锁,所以 waitnotify 方法必须放在 sychronized 同步块中。
    2. 调用 wait 方法时使用 while 进行条件判断,只有这样才能在被唤醒后继续检查 wait 的条件,并在条件没有满足的情况下继续等待。

    下面是使用 while 的示例:

    while (amount < 0) {
        sychronized(NOT_EMPTY) {
            NOT_EMPTY.wait();
        }
    }
    

    相关文章

      网友评论

          本文标题:JAVA 多线程与高并发学习笔记(七)——线程间通信

          本文链接:https://www.haomeiwen.com/subject/irfzbrtx.html