Java多线程之wait()和notify()和notifyAll()

wait

wait()

调用该方法的线程进入 WAITING状态,只有等待另外线程的通知或被中断才会返回.需要注意,调用wait()方法后,会释放对象的锁,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”),wait()需要在包裹synchronized代码块儿里面的

当一个线程从对象的wait()方法唤醒以后,它会重新去竞争锁.竞争到了会进行条件检查,如果成立就会进行下面的业务逻辑.

wait(long)
让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。

这里的参数时间是毫秒,也就是等待长达n毫秒

wait (long,int)
对于超时时间更细粒度的控制,可以达到纳秒

等待/通知机制

notify/notifyAll和wait方法,在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常,这是因为调用这几个方法前必须拿到当前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,在前面的分析中,我们知道monitor 存在于对象头的Mark Word 中(存储monitor引用指针),而synchronized关键字可以获取 monitor ,这也就是为什么notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法调用的原因.

notify()和notifyAll()

通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入WAITING状态。

notify()

线程调用notify()或者notifyAll()后,线程不会释放它自己的锁,直到该synchronized包裹的方法执行完以后,它会释放锁,所以notify()和notifyAll()一般会放在同步代码块儿的最后一行

notify()和notifyAll()需要包裹synchronized代码块儿里面的

Java 中 notify 和 notifyAll 的区别

唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

当你调用 notify 时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。虽然如果你调用 notifyAll 方法,那么等待该锁的所有线程都会被唤醒,但是在执行剩余的代码之前,所有被唤醒的线程都将争夺锁定,这就是为什么在循环上调用 wait,因为如果多个线程被唤醒,那么线程是将获得锁定将首先执行,它可能会重置等待条件,这将迫使后续线程等待。因此,notify 和 notifyAll 之间的关键区别在于 notify()只会唤醒一个线程,而 notifyAll 方法将唤醒所有线程。

何时在 Java 中使用 notify 和 notifyAll

如果所有线程都在等待相同的条件,并且一次只有一个线程可以从条件变为 true,则可以使用 notify over notifyAll。

在这种情况下,notify 是优于 notifyAll 因为唤醒所有这些因为我们知道只有一个线程会受益而所有其他线程将再次等待,所以调用 notifyAll 方法只是浪费 CPU。

尽可能用notifyall(),谨慎使用notify(),因为notify()只会唤醒一个线程,我们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程,可能我想通知唤醒的线程没有通知到.

notify/notifyAll和wait方法,在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常,这是因为调用这几个方法前必须拿到当前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,在前面的分析中,我们知道monitor 存在于对象头的Mark Word 中(存储monitor引用指针),而synchronized关键字可以获取 monitor ,这也就是为什么notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法调用的原因.

wait和Notify代码demo


 

/**
 * 先下载图片,图片下载展示附件 ,
 * 再下载附件
 */
public class Demo1 {
    
    


   public static void main(String[] args) {
    
    

      Object obj = new Object();

      Thread download = new Thread("我是下载线程 :") {
    
    
         @Override
         public void run() {
    
    

            System.out.println(this.getName() + "***开始下载图片");
            for (int i = 0; i < 101; i += 50) {
    
    
               System.out.println(this.getName() + "下载进度 : " + i + "%");
               try {
    
    
                  Thread.sleep(50);
               } catch (InterruptedException e) {
    
    
               }
            }
            System.out.println(this.getName() + "***图片下载成功");

            synchronized (obj) {
    
    
               obj.notify(); //唤醒线程
               System.err.println(this.getName() + "我释放了我自己的锁");
            }

            System.out.println(this.getName() + "***开始下载附件");
            for (int i = 0; i < 101; i += 50) {
    
    
               System.out.println(this.getName() + "附件下载进度 : " + i + "%");
            }
            try {
    
    
               Thread.sleep(50);
            } catch (InterruptedException e) {
    
    
               e.printStackTrace();
            }
            System.out.println(this.getName() + "***附件下载成功");
         }
      };
      //图片展示

      Thread show = new Thread("我是查看线程 :") {
    
    
         @Override
         public void run() {
    
    
            synchronized (obj) {
    
    
               try {
    
    
                  obj.wait();//阻塞线程等图片下载完毕
               } catch (InterruptedException e) {
    
    
                  e.printStackTrace();
               }
               System.out.println(this.getName() + "***开始展示图片");
               System.out.println(this.getName() + "***图片展示完毕");


            }
         }
      };
      /*即使是 展示线程先启动,如果图片没下载完了,也不让你展示, 让你优先运行下载线程*/


      /*同时启动两个线程,然后show线程开始等待状态,等待下载线程下载完毕之后才开启线程*/
      download.start();
      show.start();


   }

}

猜你喜欢

转载自blog.csdn.net/qq_41489540/article/details/109158287