wait与synchronized无关,wait方法必须在synchronized中使用

  wait与synchronized无关,wait方法必须在synchronized中使用

  00-1010等待/通知基础与同步一起使用等待/通知?前言:的原因分析、等待和通知问题、重现和总结

  在多线程编程中,wait方法将当前线程置于休眠状态,在另一个线程调用notify或notifyAll方法之前,不能恢复执行。但在Java中,wait和notify/notifyAll都有自己的格式要求,即在使用wait和notify(notify all的用法和notify类似,下面只使用notify来指代它们)时,必须和synchronized一起使用。

  

目录

wait 和 notify 的基础方法如下:

 

  对象锁=新对象();new thread(()-{ synchronized(lock){ try { system . out . println( before waiting ));//调用等待方法lock . wait();system . out . println(" after wait ");} catch(interrupted exception e){ e . printstacktrace();} }}).start();thread . sleep(100);synchronized(lock){ system . out . println( execute notify );//调用通知方法lock . notify();}以上代码的执行结果如下图所示:

  00-1010就是这个问题。等待和通知必须和同步一起使用吗?等待和通知可以分开使用吗?我们尝试将以上代码中的 synchronized 代码行删除,实现代码如下:

  乍一看,代码好像没什么问题,编译器也不报错。它似乎“工作正常”,然而当我们运行以上程序时就会发生如下错误:.

  无论从上述结果可以看出:是wait还是notify,如果不与synchronized一起使用,都会在程序运行时将IllegalMonitorStateException作为非法监视器状态异常上报,notify无法实现程序的唤醒功能。

  00-1010从上面的错误消息中我们可以看到,JVM在运行时会检查wait和notify是否在同步的代码中,如果不在,会报告非法的监视器状态异常(IllegalMonitorStateException),但这只是运行时的程序外观,那么Java为什么要这样设计呢?其实,之所以这样设计,就是为了防止多线程并发运行时,程序的执行混乱问题.这句话乍一看,似乎是用来形容“锁”的。但是,实际情况是一样的。等待和通知引入了锁,以避免并发执行时程序执行的混乱。这个“实现混乱问题”到底是什么?接下来,我们继续往下看。

  00-1010我们假设等待和通知可以解锁。我们用它们来实现一个定制的阻塞队列。这里的阻塞队列指的是读操作的阻塞,即在读数据时,如果有数据就返回数据,如果没有数据就阻塞等待数据,实现代码如下:.

  class blocking queue {//用于保存数据的集合queue string queue=new linked list();/* * * add方法*/public void put(String data){//queue join data queue . add(data);//唤醒线程继续执行(此处

  的线程指的是执行 take 方法的线程) notify(); // ③ } /** * 获取方法(阻塞式执行) * 如果队列里面有数据则返回数据,如果没有数据就阻塞等待数据 * @return */ public String take() throws InterruptedException { // 使用 while 判断是否有数据(这里使用 while 而非 if 是为了防止虚假唤醒) while (queue.isEmpty()) { // ① // 没有任务,先阻塞等待 wait(); // ② } return queue.remove(); // 返回数据 }}注意上述代码,我们在代码中标识了三个关键执行步骤:①:判断队列中是否有数据;②:执行 wait 休眠操作;③:给队列中添加数据并唤醒阻塞线程。 如果不强制要求添加 synchronized那么就会出现如下问题:

  步骤线程1线程21执行步骤 ① 判断当前队列中没有数据2执行步骤 ③ 将数据添加到队列,并唤醒线程1继续执行3执行步骤 ② 线程 1 进入休眠状态从上述执行流程看出问题了吗?如果 wait 和 notify 不强制要求加锁,那么在线程 1 执行完判断之后,尚未执行休眠之前,此时另一个线程添加数据到队列中。然而这时线程 1 已经执行过判断了,所以就会直接进入休眠状态,从而导致队列中的那条数据永久性不能被读取,这就是程序并发运行时执行结果混乱的问题。然而如果配合 synchronized 一起使用的话,代码就会变成以下这样:

  

class MyBlockingQueue { // 用来保存任务的集合 Queue<String> queue = new LinkedList<>(); /** * 添加方法 */ public void put(String data) { synchronized (MyBlockingQueue.class) { // 队列加入数据 queue.add(data); // 为了防止 take 方法阻塞休眠,这里需要调用唤醒方法 notify notify(); // ③ } } /** * 获取方法(阻塞式执行) * 如果队列里面有数据则返回数据,如果没有数据就阻塞等待数据 * @return */ public String take() throws InterruptedException { synchronized (MyBlockingQueue.class) { // 使用 while 判断是否有数据(这里使用 while 而非 if 是为了防止虚假唤醒) while (queue.isEmpty()) { // ① // 没有任务,先阻塞等待 wait(); // ② } } return queue.remove(); // 返回数据 }}

这样改造之后,关键步骤 ① 和关键步骤 ② 就可以一起执行了,从而当线程执行了步骤 ③ 之后,线程 1 就可以读取到队列中的那条数据了,它们的执行流程如下:

 

  步骤线程1线程21执行步骤 ① 判断当前队列没有数据2执行步骤 ② 线程进入休眠状态3执行步骤 ③ 将数据添加到队列,并执行唤醒操作4线程被唤醒,继续执行5判断队列中有数据,返回数据这样咱们的程序就可以正常执行了,这就是为什么 Java 设计一定要让 wait 和 notify 配合上 synchronized 一起使用的原因了。

  

 

  

总结

本文介绍了 wait 和 notify 的基础使用,以及为什么 wait 和 notify/notifyAll 一定要配合 synchronized 使用的原因。如果 wait 和 notify/notifyAll 不强制和 synchronized 一起使用,那么在多线程执行时,就会出现 wait 执行了一半,然后又执行了添加数据和 notify 的操作,从而导致线程一直休眠的缺陷。

 

  到此这篇关于为什么wait和notify必须放在synchronized中使用的文章就介绍到这了,更多相关wait与notify的使用内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: