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

2022-07-12  本文已影响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();
    }
}
上一篇下一篇

猜你喜欢

热点阅读