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{ Queuebuffer = 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()方法,那么可能出现什么异常呢?
首先,消费者线程调用 take 方法并判断 buffer.isEmpty 方法是否返回 true,若为 true 代表buffer是空的,则线程希望进入等待,但是在线程调用 wait 方法之前(while(buffer.isEmpty())之后),就被调度器暂停了,所以此时还没来得及执行 wait 方法。
此时生产者开始运行,执行了整个 give 方法,它往 buffer 中添加了数据,并执行了 notify 方法,但 notify 并没有任何效果,因为消费者线程的 wait 方法没来得及执行,所以没有线程在等待被唤醒。
此时,刚才被调度器暂停的消费者线程回来继续执行 wait 方法并进入了等待,这时消费者便有可能陷入无穷无尽的等待,因为它错过了刚才 give 方法内的 notify 的唤醒。
ps:上面说的调度器暂停线程,因为在多线程下,CPU 的调度是以时间片为单位进行分配的,每个线程都可以得到一定量的时间片。但如果线程拥有的时间片耗尽,它将会被暂停执行并让出 CPU 资源给其他线程。而代码中的“判断-执行”不是一个原子操作,它在中间有可能被打断,是线程不安全的,所以说有可能在线程调用 wait 方法之前这个线程就被暂停了。
1.2 wait()方法正确使用方式
class BlockingQueue{ Queuebuffer = 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 不同点
wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。
在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁。
sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。
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://ybzwz.com/article/ipcode.html