Java 线程通信之 wait/notify 机制

前言

Java 线程通信是将多个独立的线程个体进行关联处理,使得线程与线程之间能进行相互通信。比如线程 A 修改了对象的值,然后通知给线程 B,使线程 B 能够知道线程 A 修改的值,这就是线程通信。

wait/notify 机制

一个线程调用 Object 的 wait() 方法,使其线程被阻塞;另一线程调用 Object 的 notify()/notifyAll() 方法,wait() 阻塞的线程继续执行。

wai/notify 方法

方法说明
wait()当前线程被阻塞,线程进入 WAITING 状态
wait(long)设置线程阻塞时长,线程会进入 TIMED_WAITING 状态。如果设置时间内(毫秒)没有通知,则超时返回
wait(long, int)纳秒级别的线程阻塞时长设置
notify()通知同一个对象上已执行 wait() 方法且获得对象锁的等待线程
notifyAll()通知同一对象上所有等待的线程

实现 wait/notify 机制的条件:

  • 调用 wait 线程和 notify 线程必须拥有相同对象锁。
  • wait() 方法和 notify()/notifyAll() 方法必须在 Synchronized 方法或代码块中。

由于 wait/notify 方法是定义在java.lang.Object中,所以在任何 Java 对象上都可以使用。

wait 方法

在执行 wait() 方法前,当前线程必须已获得对象锁。调用它时会阻塞当前线程,进入等待状态,在当前 wait() 处暂停线程。同时,wait() 方法执行后,会立即释放获得的对象锁。

下面通过案例来查看 wait() 释放锁。

首先查看不使用 wait() 方法时的代码执行情况:

package top.ytao.demo.thread.waitnotify;

/**
 * Created by YangTao
 */
public class WaitTest {

    static Object object = new Object();

    public static void main(String[] args) {

        new Thread(() -> {
            synchronized (object){
                System.out.println("开始线程 A");
                try {
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("结束线程 A");
            }
        }, "线程 A").start();


        new Thread(() -> {
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object){
                System.out.println("开始线程 B");

                System.out.println("结束线程 B");
            }
        }, "线程 B").start();

    }

}

创建 A、B 两个线程,。首先在 B 线程创建后 sleep ,保证 B 线程的打印后于 A 线程执行。在 A 线程中,获取到对象锁后,sleep 一段时间,且时间大于 B 线程的 sleep 时间。

执行结果为:

从上图结果中,可以看到,B 线程一定等 A 线程执行完 synchronize 代码块释放对象锁后 A 线程再获取对象锁进入 synchronize 代码块中。在这过程中,Thread.sleep() 方法也不会释放锁。

当前在 A 线程 synchronize 代码块中执行 wait() 方法后,就会主动释放对象锁,A 线程代码如下:

new Thread(() -> {
    synchronized (object){
        System.out.println("开始线程 A");
        try {
            // 调用 object 对象的 wait 方法
            object.wait();
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("结束线程 A");
    }
}, "线程 A").start();

执行结果:

同时 A 线程一直处于阻塞状态,不会打印结束线程 A

wait(long) 方法是设置超时时间,当等待时间大于设置的超时时间后,会继续往 wait(long) 方法后的代码执行。

new Thread(() -> {
    synchronized (object){
        System.out.println("开始线程 A");
        try {
            object.wait(1000);
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("结束线程 A");
    }
}, "线程 A").start();

执行结果

同理,wait(long, int) 方法与 wait(long) 同样,只是多个纳秒级别的时间设置。

notify 方法

同样,在执行 notify() 方法前,当前线程也必须已获得线程锁。调用 notify() 方法后,会通知一个执行了 wait() 方法的阻塞等待线程,使该等待线程重新获取到对象锁,然后继续执行 wait() 后面的代码。但是,与 wait() 方法不同,执行 notify() 后,不会立即释放对象锁,而需要执行完 synchronize 的代码块或方法才会释放锁,所以接收通知的线程也不会立即获得锁,也需要等待执行 notify() 方法的线程释放锁后再获取锁。

notify()

下面是 notify() 方法的使用,实现一个完整的 wait/notify 的例子,同时验证发出通知后,执行 notify() 方法的线程是否立即释放锁,执行 wait() 方法的线程是否立即获取锁。

package top.ytao.demo.thread.waitnotify;

/**
 * Created by YangTao
 */
public class WaitNotifyTest {

    static Object object = new Object();

    public static void main(String[] args) {
        System.out.println();

        new Thread(() -> {
            synchronized (object){
                System.out.println("开始线程 A");
                try {
                    object.wait();
                    System.out.println("A 线程重新获取到锁,继续进行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("结束线程 A");
            }
        }, "线程 A").start();


        new Thread(() -> {
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object){
                System.out.println("开始线程 B");
                object.notify();
                System.out.println("线程 B 通知完线程 A");
                try {
                    // 试验执行完 notify() 方法后,A 线程是否能立即获取到锁
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("结束线程 B");
            }
        }, "线程 B").start();

    }

}

以上 A 线程执行 wait() 方法,B 线程执行 notify() 方法,执行结果为:

执行结果中可以看到,B 线程执行 notify() 方法后,即使 sleep 了,A 线程也没有获取到锁,可知,notify() 方法并没有释放锁。

notify() 是通知到等待中的线程,但是调用一次 notify() 方法,只能通知到一个执行 wait() 方法的等待线程。如果有多个等待状态的线程,则需多次调用 notify() 方法,通知到线程顺序则根据执行 wait() 方法的先后顺序进行通知。

下面创建有两个执行 wait() 方法的线程的代码:

package top.ytao.demo.thread.waitnotify;

/**
 * Created by YangTao
 */
public class MultiWaitNotifyTest {

    static Object object = new Object();

    public static void main(String[] args) {
        System.out.println();

        new Thread(() -> {
            synchronized (object){
                System.out.println("开始线程 A");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("结束线程 A");
            }
        }, "线程 A").start();


        new Thread(() -> {
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object){
                System.out.println("开始线程 B");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("结束线程 B");
            }
        }, "线程 B").start();


        new Thread(() -> {
            try {
                Thread.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object){
                System.out.println("开始通知线程 C");
                object.notify();
                object.notify();
                System.out.println("结束通知线程 C");
            }
        }, "线程 C").start();

    }

}

先 A 线程执行 wait() 方法,然后 B 线程执行 wait() 方法,最后 C 线程调用两次 notify() 方法,执行结果:

notifyAll()

通知多个等待状态的线程,通过多次调用 notify() 方法实现的方案,在实际应用过程中,实现过程不太友好,如果是想通知所有等待状态的线程,可使用 notifyAll() 方法,就能唤醒所有线程。

实现方式,只需将上面 C 线程的多次调用 notify() 方法部分改为调用一次 notifyAll() 方法即可。

new Thread(() -> {
    try {
        Thread.sleep(3000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    synchronized (object){
        System.out.println("开始通知线程 C");
        object.notifyAll();
        System.out.println("结束通知线程 C");
    }
}, "线程 C").start();

执行结果:

根据不同 JVM 的实现,notifyAll() 的唤醒顺序会有所不同,当前测试环境中,以倒序顺序唤醒线程。

实现生产者消费者模式

生产消费者模式就是一个线程生产数据进行存储,另一线程进行数据提取消费。下面就以两个线程来模拟,生产者生成一个 UUID 存放到 List 对象中,消费者读取 List 对象中的数据,读取完成后进行清除。

实现代码如下:

package top.ytao.demo.thread.waitnotify;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Created by YangTao
 */
public class WaitNotifyModelTest {

    // 存储生产者产生的数据
    static List<String> list = new ArrayList<>();

    public static void main(String[] args) {

        new Thread(() -> {
            while (true){
                synchronized (list){
                    // 判断 list 中是否有数据,如果有数据的话,就进入等待状态,等数据消费完
                    if (list.size() != 0){
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    // list 中没有数据时,产生数据添加到 list 中
                    list.add(UUID.randomUUID().toString());
                    list.notify();
                    System.out.println(Thread.currentThread().getName() + list);
                }
            }
        }, "生产者线程 A ").start();


        new Thread(() -> {
            while (true){
                synchronized (list){
                    // 如果 list 中没有数据,则进入等待状态,等收到有数据通知后再继续运行
                    if (list.size() == 0){
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    // 有数据时,读取数据
                    System.out.println(Thread.currentThread().getName() + list);
                    list.notify();
                    // 读取完毕,将当前这条 UUID 数据进行清除
                    list.clear();
                }
            }
        }, "消费者线程 B ").start();

    }

}

运行结果:

生产者线程运行时,如果已存在未消费的数据,则当前线程进入等待状态,收到通知后,表明数据已消费完,再继续向 list 中添加数据。

消费者线程运行时,如果不存在未消费的数据,则当前线程进入等待状态,收到通知后,表明 List 中已有新数据被添加,继续执行代码消费数据并清除。

不管是生产者还是消费者,基于对象锁,一次只能一个线程能获取到,如果生产者获取到锁就校验是否需要生成数据,如果消费者获取到锁就校验是否有数据可消费。

一个简单的生产者消费者模式就以完成。

总结

等待/通知机制是实现 Java 线程间通信的一种方式,将多线程中,各个独立运行的线程通过相互通信来更高效的协作完成工作,更大效率利用 CPU 处理程序。这也是学习或研究 Java 线程的必学知识点。

推荐阅读

《Java 线程基础,从这篇开始》

《你必须会的 JDK 动态代理和 CGLIB 动态代理》

《Dubbo 系列》



个人博客: https://ytao.top

关注公众号 【ytao】,更多原创好文

我的公众号

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java中的waitnotify是多线程编程中的两个重要方法,用于线程之间的协作和通信wait方法可以使当前线程进入等待状态,直到其他线程调用notifynotifyAll方法唤醒它。在调用wait方法时,当前线程会释放它所持有的锁,以便其他线程可以访问共享资源。 notify方法用于唤醒一个处于等待状态的线程,如果有多个线程在等待,则只会唤醒其中一个线程notifyAll方法则会唤醒所有处于等待状态的线程waitnotify方法必须在同步块中使用,即在使用这两个方法的对象上获取锁。否则会抛出IllegalMonitorStateException异常。 使用waitnotify方法可以实现线程之间的协作和通信,例如生产者消费者模型。在生产者消费者模型中,生产者线程生产数据并将其放入共享队列中,消费者线程从队列中取出数据并进行消费。当队列为空时,消费者线程需要等待生产者线程生产数据,此时可以使用wait方法使消费者线程进入等待状态。当生产者线程生产数据并将其放入队列中时,可以使用notify方法唤醒处于等待状态的消费者线程。 ### 回答2: Java线程中的 waitnotify 是两个非常重要的方法,它们可以帮助线程之间达成协作,实现复杂的操作。wait 方法用于让当前线程进入等待状态,直到其他线程通过 notify 方法通知它继续执行。notify 方法则用于唤醒一个等待状态的线程,使其继续执行。 wait 方法 wait 方法用于让当前线程进入等待状态,直到其他线程通过 notifynotifyAll 方法唤醒它。wait 方法需要在 synchronized 代码块中使用,否则会抛出 IllegalMonitorStateException 异常。在进入等待状态之后,线程将释放锁,并且进入一个等待池中,等待其他线程调用 notifynotifyAll 方法唤醒它。 notify 方法 notify 方法用于唤醒一个等待状态的线程,使其继续执行。notify 方法同样需要在 synchronized 代码块中使用,否则同样会抛出 IllegalMonitorStateException 异常。当一个线程调用 notify 方法时,等待池中的线程将会被唤醒,但是它们不能马上继续执行,必须等待当前线程释放锁。如果有多个线程在等待池中,notify 方法只会唤醒其中一个线程,具体唤醒哪个线程是随机的。 notifyAll 方法 notifyAll 方法与 notify 方法类似,但是它会唤醒所有等待池中的线程notifyAll 方法同样需要在 synchronized 代码块中使用。 使用 waitnotify 实现线程协作 waitnotify 方法可以用来实现线程之间的协作,例如生产者和消费者问题。假设我们有一个共享的队列,生产者向队列中添加数据,消费者从队列中取出数据。如果队列已经满了,生产者就需要等待消费者取走数据,如果队列是空的,消费者就需要等待生产者加入新数据。 在这个问题中,我们可以使用 waitnotify 方法来实现线程之间的协作,代码如下: ``` public class Queue { private final List<Integer> items = new LinkedList<>(); private static final int MAX_SIZE = 10; public synchronized void produce(int item) throws InterruptedException { while (items.size() == MAX_SIZE) { wait(); } items.add(item); notify(); } public synchronized int consume() throws InterruptedException { while (items.isEmpty()) { wait(); } int item = items.remove(0); notify(); return item; } } public class Producer implements Runnable { private final Queue queue; public Producer(Queue queue) { this.queue = queue; } public void run() { for (int i = 0; i < 20; i++) { try { queue.produce(i); System.out.println("Produced: " + i); } catch (InterruptedException ex) { ex.printStackTrace(); } } } } public class Consumer implements Runnable { private final Queue queue; public Consumer(Queue queue) { this.queue = queue; } public void run() { for (int i = 0; i < 20; i++) { try { int item = queue.consume(); System.out.println("Consumed: " + item); } catch (InterruptedException ex) { ex.printStackTrace(); } } } } public class Main { public static void main(String[] args) throws InterruptedException { Queue queue = new Queue(); Thread producer = new Thread(new Producer(queue)); Thread consumer = new Thread(new Consumer(queue)); producer.start(); consumer.start(); producer.join(); consumer.join(); } } ``` 在这个示例代码中,我们创建了一个 Queue 类,它有两个方法 produce 和 consume 用于生产和消费数据。在 produce 方法中,我们使用 while 循环来等待队列不满,如果队列已经满了,就调用 wait 方法进入等待状态。在 consume 方法中,我们使用 while 循环来等待队列不空,如果队列是空的,就调用 wait 方法进入等待状态。在生产新数据或者消费数据之后,我们都调用 notify 方法来唤醒等待池中的线程。 最后,我们可以使用 Producer 和 Consumer 类来生产和消费数据,它们分别运行在不同的线程中。在运行这个程序时,生产者将不断生产数据,消费者将不断消费数据,一直到数据生产完毕为止。在这个过程中,生产者和消费者之间通过 waitnotify 方法实现了线程之间的协作。 ### 回答3: Java是一种支持多线程的编程语言,在多线程编程过程中,一个线程可能需要等待另一个线程的某个条件满足后才能继续执行。Java提供了waitnotify来实现线程之间的协作。 wait:使当前线程进入等待状态,释放对象的锁,直到其他线程调用notifynotifyAll方法唤醒它。wait方法必须在持有对象锁的情况下调用,否则会抛出IllegalMonitorStateException异常。 notify:唤醒一个处于等待状态的线程,如果有多个线程等待,则只会唤醒其中一个,具体唤醒哪个线程无法预测。 notifyAll:唤醒所有处于等待状态的线程waitnotify必须在同步代码块中调用,并且针对同一个对象。waitnotify的调用顺序也非常重要,如果先调用了notify而没有等待线程,会导致唤醒失效。 在多线程编程中,waitnotify常常用于生产者和消费者模式中的线程之间的通信,生产者线程在生产完毕后调用notify方法唤醒消费者线程来消费数据,消费者线程在消费完毕后调用wait方法等待下一个生产者线程的唤醒。 waitnotify的使用需要谨慎,如果使用不当,会导致死锁或线程饥饿等问题。同时,在Java SE 5之后,Java提供了更加高级的线程库,如ReentrantLock、Condition等,可以更加方便和安全地实现线程之间的协作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值