Java线程间协作wait、notify和notifyAll详解

 更新时间:2023年10月26日 10:00:12   作者:chengmaoning  
这篇文章主要介绍了Java线程间协作wait、notify和notifyAll详解,在 Java 中可以用 wait、notify 和 notifyAll 来实现线程间的通信,尽管关于wait和notify的概念很基础,它们也都是Object类的函数,但用它们来写代码却并不简单,,需要的朋友可以参考下

概要描述

在 Java 中可以用 wait、notify 和 notifyAll 来实现线程间的通信。尽管关于wait和notify的概念很基础,它们也都是Object类的函数,但用它们来写代码却并不简单。

wait, notify, notifyAll 都是基类Object的方法,而不属于Thread,这让习惯了调用Thread.sleep()使线程阻塞的同学感到奇怪。不过这样设计是有道理的,因为这些方法操作的锁(monitor)也是对象的一部分。可见,与sleep不同,通过调用共享对象的wait方法使当前线程等待;通过调用对象的notify, notifyAll 方法唤醒该对象上的等待线程。

先来看官方文档,Java doc对wait方法的描述:

public final void wait() throws InterruptedException

Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).

The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.

As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:

     synchronized (obj) {
         while (<condition does not hold>)
             obj.wait();
         ... // Perform action appropriate to condition
     }

This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor.

Throws:
    IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
    InterruptedException - if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown. 

Java doc对notify的描述:

public final void notify()

Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the wait methods.

The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.

This method should only be called by a thread that is the owner of this object's monitor. A thread becomes the owner of the object's monitor in one of three ways:

    By executing a synchronized instance method of that object.
    By executing the body of a synchronized statement that synchronizes on the object.
    For objects of type Class, by executing a synchronized static method of that class. 

Only one thread at a time can own an object's monitor.

Throws:
    IllegalMonitorStateException - if the current thread is not the owner of this object's monitor.

生产者-消费者示例:

/**
 * 
 */
public class Producer extends Thread {

    private volatile Queue<Integer> queue;
    private int maxSize;

    public Producer(Queue<Integer> queue, int maxSize) {
        this.queue = queue;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        while (true) {
            //wait,notify方法必须在同步代码中运行
            synchronized (queue) {
                //条件一定在循环中判断,以防死锁
                while (queue.size() == maxSize) {
                    try {                             System.out.println(Thread.currentThread().getName() + " wait.");
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                int i = new Random().nextInt();
                System.out.println(Thread.currentThread().getName() + " produce: " + i);
                queue.add(i);
                queue.notifyAll();
            }
        }
    }
}

消费者:

 /**
 * 
 */
public class Consumer extends Thread {

    private volatile Queue<Integer> queue;
    private int maxSize;

    public Consumer(Queue<Integer> queue, int maxSize) {
        this.queue = queue;
        this.maxSize = maxSize;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (queue) {
                while (queue.isEmpty()) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " wait.");
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(Thread.currentThread().getName() + " consume: " + queue.remove());
                queue.notifyAll();
            }
        }
    }
}

main方法:

/**
 * 
 */
public class WaitNotifyMain {

    private volatile static Queue<Integer> queue = new LinkedList<>();

    /**
     * @param args
     */
    public static void main(String[] args) {

        Producer producer = new Producer(queue, 10);
        producer.setName("Producer");//设置线程名称
        Consumer consumer = new Consumer(queue, 10);
        consumer.setName("Consumer");

        producer.start();
        consumer.start();

    }

}

打印输出:

Producer produce: -2017386252
Producer produce: 1186339917
Producer produce: -674757828
Producer produce: 605757848
Producer produce: -539314860
Producer produce: 490590935
Producer produce: -845855520
Producer produce: -1459720588
Producer produce: 1274488529
Producer produce: 55225134
Producer wait.
Consumer consume: -2017386252
Consumer consume: 1186339917
Consumer consume: -674757828
Consumer consume: 605757848
Consumer consume: -539314860
Consumer consume: 490590935
Consumer consume: -845855520
Consumer consume: -1459720588
Consumer consume: 1274488529
Consumer consume: 55225134
Consumer wait.
Producer produce: 673397316
Producer produce: -1176693368
Producer produce: -1707265532
Producer produce: -1614197913
Producer produce: 306171031
Producer produce: -1646438955
Producer produce: 1141572321
Producer produce: 1235215288
Producer produce: -692805724
Producer produce: -2131184778
Producer wait.
Consumer consume: 673397316
Consumer consume: -1176693368

总结

wait, notify, notifyAll 是共享对象上的调用,而不是线程对象的调用。

wait, notify, notifyAll一定要在共享对象同步方法或同步代码块中执行,否则会在运行时抛出IllegalMonitorStateException的异常。因为wait, notify, notifyAll包含了对共享对象锁的操作,所以之前一定要先synchronized获取对象锁。

在共享对象上调用wait()时,当前线程进入等待状态, 并释放刚获取的对象锁(Thread.sleep()是不释放锁的),让出CPU, 此时,其他线程可以调用共享对象的同步方法或代码块。

唤醒线程在共享对象上执行notify会随机唤醒该对象的其中之一等待线程;唤醒线程在共享对象上执行notifyAll会唤醒该对象上的所有等待线程;这里要着重注意两点

A,唤醒线程执行完共享对象的notify或notifyAll方法后,仍然要执行完synchronized修饰的同步代码块中后面的代码才能释放对象锁,因此,通常notify后面尽量减少执行代码,让对象锁尽快释放。

B, 唤醒是指线程ready的状态,尚未运行,共享对象的锁被唤醒线程释放后,ready状态的线程跟普通线程一样需要竞争共享对象的锁,执行同步代码块中wait()后面的代码。

永远在循环(loop)里调用 wait 和 notify, notifyAll,不是在 If 语句,避免死锁情况发生。

基于以上认知,下面这个是使用wait和notify函数的规范代码模板:

// The standard idiom for calling the wait method in Java 
synchronized (sharedObject) { 
    while (condition) { 
    sharedObject.wait(); 
        // (Releases lock, and reacquires on wakeup) 
    } 
    // do action based upon condition e.g. take or put into queue 
}

在while循环里使用wait的目的,是在线程被唤醒的前后都持续检查条件是否被满足。如果条件并未改变,wait被调用之前notify的唤醒通知就来了,那么这个线程并不能保证被唤醒,有可能会导致死锁问题。

到此这篇关于Java线程间协作wait、notify和notifyAll详解的文章就介绍到这了,更多相关Java线程间协作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JAVA实现Base64编码的三种方式

    JAVA实现Base64编码的三种方式

    本文主要介绍了JAVA实现Base64编码的三种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • SpringBoot整合Camunda工作流实践

    SpringBoot整合Camunda工作流实践

    工作流是任务序列的组织方式,BPMN是业务流程建模标准,Activiti、Flowable、Camunda均源自JBPM,通过分叉发展形成不同技术路线,支持多种数据库,且可与SpringBoot集成部署流程
    2025-09-09
  • 一文详解Spring事务的实现与本质

    一文详解Spring事务的实现与本质

    这篇文章主要介绍了Spring中事务的两种实现方式:声明式事务、编程式事务以及他们的本质。文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-04-04
  • JAVA线程用法详解

    JAVA线程用法详解

    这篇文章主要介绍了JAVA线程用法,配合实例针对Java中线程的开启、sleep、合并与让出等进行了较为深入的分析,需要的朋友可以参考下
    2014-08-08
  • 解决spring项目找不到Aspect依赖注解的问题

    解决spring项目找不到Aspect依赖注解的问题

    这篇文章主要介绍了解决spring项目找不到Aspect依赖注解的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Java面向对象选择题总结归纳

    Java面向对象选择题总结归纳

    今天小编就为大家分享一篇关于Java面向对象选择题总结归纳,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • Reactive Programming入门概念详解

    Reactive Programming入门概念详解

    这篇文章主要为大家介绍了Reactive Programming入门概念详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • springboot系统首页自动跳转拼接到index的实现

    springboot系统首页自动跳转拼接到index的实现

    文章介绍了通过访问http://localhost:8091时,服务器会动态跳转到系统的欢迎页面,实现原理是在程序启动时自动加载默认的请求路径,并动态拼接前缀和后缀,最终指向./WEB-INF/views/index.jsp,作者分享了这一经验,并希望得到大家的支持
    2025-11-11
  • 使用springboot防止反编译proguard+xjar

    使用springboot防止反编译proguard+xjar

    介绍了三种代码混淆和加密工具的使用方法:ProGuard、Xjar和ClassFinal,ProGuard用于混淆Java字节码,Xjar提供对JAR包内资源的加密和动态解密,而ClassFinal则支持直接加密JAR包或WAR包,通过预研和实际操作
    2024-11-11
  • Springboot集成任务调度实现过程

    Springboot集成任务调度实现过程

    这篇文章主要介绍了Springboot集成任务调度实现过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04

最新评论