java的wait/notify/notifyAll方法怎么正确使用

本篇内容介绍了“java的wait/notify/notifyAll方法怎么正确使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

韩城网站建设公司创新互联公司,韩城网站设计制作,有大型网站制作公司丰富经验。已为韩城近1000家提供企业网站建设服务。企业网站搭建\外贸网站建设要多少钱,请找那个售后服务好的韩城做网站的公司定做!

一:为什么 wait 必须在 synchronized 保护的同步代码中使用?

源码中对wait方法的介绍如下:

        /**
         * 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 {@code notify} method for a          * description of the ways in which a thread can become the owner of          * a monitor.          *          */         public final void wait() throws InterruptedException {             wait(0);         }

意思是说,在使用 wait 方法时,必须把 wait 方法写在 synchronized 保护的 while 代码块中,并始终判断执行条件是否满足,如果满足就往下继续执行,如果不满足就执行 wait 方法,而在执行 wait 方法之前,必须先持有对象的 monitor 锁,也就是通常所说的 synchronized 锁

1.1 不这样做会导致什么问题?

    class BlockingQueue{

        Queue buffer = new LinkedList<>();
        public void give(String data){
            buffer.add(data);
            notify();
        }
        public String take() throws InterruptedException {
            while(buffer.isEmpty()){
                wait();
            }
            return buffer.remove();
        }
    }

在代码中可以看到有两个方法,give 方法负责往 buffer 中添加数据,添加完之后执行 notify 方法来唤醒之前等待的线程,而 take 方法负责检查整个 buffer 是否为空,如果为空就进入等待,如果不为空就取出一个数据,这是典型的生产者消费者的思想。

在如上所示的代码中没有正确使用wait()方法,那么可能出现什么异常呢?

  1. 首先,消费者线程调用 take 方法并判断 buffer.isEmpty 方法是否返回 true,若为 true 代表buffer是空的,则线程希望进入等待,但是在线程调用 wait 方法之前(while(buffer.isEmpty())之后),就被调度器暂停了,所以此时还没来得及执行 wait 方法

  2. 此时生产者开始运行,执行了整个 give 方法,它往 buffer 中添加了数据,并执行了 notify 方法,但 notify 并没有任何效果,因为消费者线程的 wait 方法没来得及执行,所以没有线程在等待被唤醒

  3. 此时,刚才被调度器暂停的消费者线程回来继续执行 wait 方法并进入了等待,这时消费者便有可能陷入无穷无尽的等待,因为它错过了刚才 give 方法内的 notify 的唤醒。

ps:上面说的调度器暂停线程,因为在多线程下,CPU 的调度是以时间片为单位进行分配的,每个线程都可以得到一定量的时间片。但如果线程拥有的时间片耗尽,它将会被暂停执行并让出 CPU 资源给其他线程。而代码中的“判断-执行”不是一个原子操作,它在中间有可能被打断,是线程不安全的,所以说有可能在线程调用 wait 方法之前这个线程就被暂停了。

1.2 wait()方法正确使用方式

    class BlockingQueue{
        Queue buffer = new LinkedList<>();
        public void give(String data){
            synchronized (this){
                buffer.add(data);
                notify();
            }
        }
        public String take() throws InterruptedException {
            synchronized (this){
                while(buffer.isEmpty()){
                    wait();
                }
                return buffer.remove();
            }
        }
    }

这样就可以确保 notify 方法永远不会在 buffer.isEmpty 和 wait 方法之间被调用,提升了程序的安全性。另外,wait 方法会释放 monitor 锁,这也要求我们必须首先进入到 synchronized 内持有这把锁。

1.2.1 使用while结构判断可以避免虚假唤醒问题

线程可能在既没有被notify/notifyAll,也没有被中断或者超时的情况下被唤醒,这种唤醒是我们不希望看到的。然在实际生产中,虚假唤醒发生的概率很小,但是程序依然需要保证在发生虚假唤醒的时候的正确性,所以就需要采用while循环的结构。

while (condition does not hold)
    obj.wait();

这样即便被虚假唤醒了,也会再次检查while里面的条件,如果不满足条件,就会继续wait,也就消除了虚假唤醒的风险。

二:wait 和 sleep 方法的异同

2.1 相同点

  • 它们都可以让线程阻塞

  • 它们都可以响应 interrupt 中断:在等待的过程中如果收到中断信号,都可以进行响应,并抛出 InterruptedException 异常。

2.2 不同点

  1. wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求

  2. 在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁

  3. sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。

  4. wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中

2.2.1 为什么wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?

首先因为 Java 中每个对象都有一把称之为 monitor 监视器的锁,每个对象都可以上锁,在对象头中有一个用来保存锁信息的位置。这个锁是对象级别的,而非线程级别的,wait/notify/notifyAll 也都是锁级别的操作,它们的锁属于对象,所以把它们定义在 Object 类中是最合适,因为 Object 类是所有对象的父类。

其次,一个线程可能持有多把锁,以便实现相互配合的复杂逻辑,既然我们是让当前线程去等待某个对象的锁,自然应该通过操作对象来实现,而不是操作线程。

“java的wait/notify/notifyAll方法怎么正确使用”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!


新闻名称:java的wait/notify/notifyAll方法怎么正确使用
文章出自:http://myzitong.com/article/ipcode.html