【问题标题】:Does notify/notifyall release the lock being heldnotify/notifyall 是否释放被持有的锁
【发布时间】:2011-05-14 01:11:15
【问题描述】:

我对等待和通知/通知所有有点困惑。

我知道每个 java 对象都有一个锁。我知道等待会释放其他线程的锁。 notify/notifyall 怎么样? notify/notifyAll 是否释放它为其他线程持有的锁?

【问题讨论】:

    标签: java multithreading locking


    【解决方案1】:

    不——notify/notifyAll 不会像wait 那样释放锁。直到调用notify的代码释放它的锁,被唤醒的线程才能运行。

    Javadoc 是这么说的:

    线程释放此监视器的所有权并等待,直到另一个线程通过调用 notify 方法或 notifyAll 方法通知在此对象的监视器上等待的线程唤醒。然后线程等待直到它可以重新获得监视器的所有权并恢复执行。

    【讨论】:

    • 谢谢,但根据我的阅读,notify/notifyall 将唤醒正在等待的线程,如果 notigy/notifyall 没有释放锁,等待的线程如何拿起来测试已经改变的东西?
    • 我的理解是他们必须等待锁正常释放。因此,如果我们执行synchronized (x) { x.notifyAll(); foo(); },则等待线程在foo() 完成之前无法运行。
    • @icn 当执行通知的线程退出时,锁被释放,因此它唤醒的线程之一现在可以获取锁。其他人(如果有的话)将阻塞,直到锁可用。
    • @Daniel 实际上是的,但准确地说,等待线程在当前执行的线程离开同步块(即最后一个})之前无法运行 - 当然是在foo()之后完成(或该块中的任何其他内容)。
    【解决方案2】:

    我必须不同意那些说 notifyAll() 释放锁定的人,该对象上正在同步等待和通知线程。

    一个例子:

    Consumer 类包含一个块:

    synchronized(sharedObject){
    if(sharedObject.isReadyToConsume() == false){
         sharedObject.wait();
    }else {
        sharedObject.doTheThing();
        System.out.println("consumer consuming...");
     }
    

    }

    场景:Consumer class 获得 sharedObject 对象的锁定,独占进入(它在同步块内)并看到 sharedObject 具有还没有准备好(没有什么可消耗的:)),它在 sharedObject 上调用wait() 方法。这样它就会释放锁(在那里停止执行!)并等待在另一个线程(可能是生产者)调用sharedObject.notify();sharedObject.notifyAll(); 时通知继续。当它收到通知时,它会从 wait() 行继续

    sharedObject 会跟踪要求通知它的线程。当某些线程调用 sharedObject.notifyAll() 方法时,sharedObject 会通知等待中的线程唤醒... 现在,棘手的部分是线程自然在到达其 synchronized(sharedObject){} 块的末尾时释放对象的锁。问题是如果我在该块中调用 notifyAll() 会发生什么notifyAll() 唤醒等待中的线程,但锁仍归刚刚调用 notifyAll()

    的线程所有

    看看Producer sn-p:

    synchronized(sharedObject){
    //We are exlusively working with sharedObject and noone can enter it
    [... changing the object ...]
    sharedObject.notifyAll();     //notifying the waiting threads to wake up
    
    Thread.sleep(1000);           //Telling the current thread to go to sleep. It's holding the LOCK
    System.out.println("awake...");
    

    }

    如果 notifyAll() 会释放锁,那么在 Consumer 类已经开始使用 sharedObject 之后,“唤醒...”将被打印出来。事实并非如此……输出显示,在 Producer 退出其同步块后,Consumer 正在消费 sharedObject……

    • wait() - 释放锁并在收到通知时继续下一行
    • notify(), notifyAll() - 不要释放锁。它们只是让等待线程再次可运行(而不是空闲)。他们将有权进入,当 当前线程到达其同步块的末尾并且线程 scheduler 告诉他们锁已被释放。为之而战 锁又开始了

    【讨论】:

      【解决方案3】:
      • wait() 告诉调用线程放弃监视器并进入睡眠状态,直到其他 线程进入同一个监视器并调用 notify()。

      • notify( ) 唤醒对同一对象调用 wait( ) 的线程。

      • notifyAll( ) 唤醒对同一对象调用 wait( ) 的所有线程。这 优先级最高的线程将首先运行。

      【讨论】:

      • 是的,先生,notifyAll() 会唤醒所有等待该对象的线程,它们可以运行。
      • notify() 将唤醒 a 正在等待对象的线程,不一定第一个线程。
      • 还是想澄清一下,包含notifyall的线程在调用notifyall后会立即重新加锁,或者在完成包含notifyall的方法后才重新加锁
      • @dotNET 你是说 notifyAll 自己释放锁吗?我认为情况并非如此。你能指出文档吗?
      • 编写良好的代码应该在通知后迅速释放锁,但没有要求...任何代码都可以放在通知和同步块的末尾(或方法的末尾)之间,如果它是同步方法)。
      【解决方案4】:

      假设一堆读者想要读取某些资源的更新值,这将由 Writer 更新。那么 Reader 是如何知道 Writer 已经更新了 Resource Fields 的呢。

      因此,为了在公共资源上的 Readers 和 Writers 之间同步这种情况,使用了 Object 类的三个 final 方法。

      • 等待()
      • 通知()
      • notifyAll()

      Wait :读者想要读取资源的更新值,他们注册资源对象,即当更新发生在同一对象上时,当 Writer 通知它时,读者将尝试获取资源锁定并读取更新资源。 - 等待仅在 Reader 具有锁定对象时调用,在我们的例子中它是资源。 - 一旦调用 wait 方法,Reader 就会释放 Lock Object。 - 现在只有同一个注册的对象(资源)阅读器才会收到通知信号。 - 如果 Reader 在 Object 上调用 wait,这与用于发送通知的 Object Writer 不同,Reader 将永远不会收到通知信号。 - 一旦读者收到通知,现在读者将尝试获取锁的内容(其中一个获得锁)读取资源的更新值。同样,其他 Reader 也轮流获取锁并读取更新的值。 - 一旦 Reader 读取到更新的值,执行业务逻辑并从同步块中出来,Reader 将释放锁,以便其他 Reader 可以获取它。

      通知:Writer进入Synchronized Block,获取锁后执行其业务逻辑,更新资源Object,一旦资源Object更新,将通知正在等待的线程(Readers)锁。 - 仅向一个等待线程通知信号,由底层 Java 线程管理器决定 - 一旦 Writer 发出 notify() 信号,并不意味着 Reader 会立即读取更新值。首先 writer 必须释放 Lock,一旦它从 Synchronized 块中出来就会这样做。一旦 Lock 被释放并通知等待线程,然后 [In case of notify()] 通知线程将获取锁 [Released by Writer]然后进入同步块并从他离开的地方完成[即wait()之后的语句]。

      Notify-All:在 notifyAll 中,所有注册了资源锁的线程都会收到通知。 - 一旦 notifyAll() 被触发,所有等待同一个锁的线程都将获得信号并准备好争用获取锁。 - 一旦 Writer 完成其 Job 并释放锁,任何一个 Reader 都将获得锁[哪个线程,再次由底层 Java 线程管理器实现决定]。 - 一旦阅读器获得锁,它将进入同步块,他离开的地方[即在 wait() 方法之后]执行它的任务,并在完成同步块时释放锁。 - 现在其他剩余线程将尝试获取锁,其中任何一个都将获得它,进入同步块,完成其任务然后释放锁。 - 这个过程将一直持续到所有注册读者完成那里的工作。


      现在我们将看到它的代码。此外,我们还将讨论代码。 :

      代码基本概述:它由三个类组成

      • 资源类:将在其上获取 Lock 并调用 wait() 和 notify(),notifyAll() 将被调用。
      • ReaderTask : 实现 Runnable 接口,暗示读者作业,想要读取资源对象的更新值。
      • WriterTask : 实现 Runnable 接口,暗示编写者作业,将更新资源对象并通知注册的等待线程。
      • Demo 类:将创建 3 个 Readers 和 1 个 Writer 线程,将各自的任务绑定到它们并启动线程。

      资源.java

         public class Resource {
            private String mesg;
      
                public void setMesg(String mesg){
               this.mesg =mesg;
            }
            public String getMesg(){
               return this.mesg;
            }
          }
      

      WaitThreadTask.java

      public class WaitThreadTask implements Runnable {
      
          private Resource resource;
      
          public WaitThreadTask(Resource resource){
              this.resource = resource;
          }
      
          @Override
          public void run() {
              // TODO Auto-generated method stub
              synchronized(resource){
                  System.out.println("Before Reading Updated Value By : " +Thread.currentThread().getName() );
                  //We need to Take care to get the updated value, so waiting for writer thread to update value.
                  try {
                      //Release resource Lock & wait till any notification from Writer.
                      resource.wait();
                      System.out.println("Waiting is Over For : "+ Thread.currentThread().getName());
                  } catch (InterruptedException e) {
                      // TODO Auto-generated catch block
                      e.printStackTrace();
                  }
                  //Read Updated Value
                  System.out.println("Updated Value of Resource Mesg :" + resource.getMesg() + " Read By :" +Thread.currentThread().getName());
              }
          }
      }
      

      WriterThreadTask.java

      public class WriterThreadTask implements Runnable{
      
          private Resource resource;
      
          public WriterThreadTask(Resource resource){
              this.resource = resource;
          }
      
          @Override
          public void run() {
              // TODO Auto-generated method stub
              synchronized(resource){
                  System.out.println("Before Updating Resource By : " + Thread.currentThread().getName());
                  //Updating resource Object Message
                  resource.setMesg("Hi How are You !!!");
                  resource.notify();
                  //resource.notifyAll();
                  //Once Writer Comes Out from Synch Block, Readers will Content to read the values.
                  System.out.println("Task Done By Writer Thread.");
              }
          }
      }
      

      ThreadDemo.java

      public class ThreadDemo {
      
          public static void main(String args[]){
      
              //Create Single Resource Object, which can act as Lock on Writer and Readers.
      
              Resource lock = new Resource();
      
              //Three Readers and One Writer runnable Tasks.
              Runnable taskR1 = new WaitThreadTask(lock);
              Runnable taskR2 = new WaitThreadTask(lock);
              Runnable taskR3 = new WaitThreadTask(lock);
              Runnable taskW1 = new WriterThreadTask(lock);
      
              Thread t1 = new Thread(taskR1, "Reader1");
              Thread t2 = new Thread(taskR2, "Reader2");
              Thread t3 = new Thread(taskR3, "Reader3");
              Thread t4 = new Thread(taskW1, "Writer1");
      
              t1.start();
              t2.start();
              t3.start();
      
              /*try{
                  Thread.sleep(5000);
              } catch(InterruptedException e){
                  e.printStackTrace();
              }*/
      
              t4.start();
          }
      
      }
      

      代码观察

      • notify()/notifyAll() 和 wait() 都适用于它们已经获得的锁定对象。例如: Synchornized(ObjectA) {......// ... // ObjectB.wait() or ObjectB.notify() or ObjectB.notifyAll() ...} 那么它会抛出 IllegalMonitorStateException。因此,必须注意,在调用上述三个方法中的任何一个之前,必须先获取锁。即使你只是写了 notify() 或 wait() 或 notifyAll(),它仍然会抛出 IllegalMonitorStateException 因为[它建议锁必须在此对象上获取,但事实并非如此]。
      • 阅读器将只能接收发送相同通知的信号。如果等待的对象与发送通知的对象不同,那么读者将永远不会收到通知,因此他们将永远等待。
      • 在 Writer 之前注册的读者能够发送通知,只有那些读者会收到它。因为如果 Writer 先发送通知,在 reader 注册到 Object 之前,他们将不会收到信号,因为 Signals 已经被错过了:Missed Signals
      • Reader 和 Writer 应该在同一个 Object 上获取 Lock,并且应该在同一个 Object 上调用等待/通知信号。如果将上面的代码修改为,而不是使用资源进行锁和等待和通知,如果我们使用这个。会发生什么 ?好吧..所有读者都将永远等待,因为读者注册了不同的WaitThreadTask对象,而作者在WriterThreadTask上通知。因此,所有 Reader 都不会收到通知信号,因为它们已注册为在各自的 WaitThreadTask 对象上接收信号,而不是在 WriterThreadTask 对象上。

      【讨论】:

        【解决方案5】:
        public class ProducerConsumerInJava {
            public static void main(String args[]) {
              System.out.println("How to use wait and notify method in Java"); 
              System.out.println("Solving Producer Consumper Problem"); 
              Queue<Integer> buffer = new LinkedList<>(); 
              int maxSize = 10; 
              Thread producer = new Producer(buffer, maxSize, "PRODUCER"); 
              Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); 
              producer.start(); 
              consumer.start(); 
            }
        }
        
        class Producer extends Thread {
           private Queue<Integer> queue; 
           private int maxSize; 
           public Producer(Queue<Integer> queue, int maxSize, String name){ 
                    super(name); this.queue = queue; this.maxSize = maxSize; 
           }
        
           public void run() { 
             while (true) { 
                      synchronized (queue) { 
                            while (queue.size() == maxSize) { 
                                  try { 
                                        System.out .println("Queue is full, " +                         
                                 "Producer thread waiting for " + "consumer to take 
                                  something from queue"); 
                                  queue.wait(); 
                            } catch (Exception ex) { 
                                ex.printStackTrace(); 
                                } 
                        }
              Random random = new Random(); 
              int i = random.nextInt(); 
              System.out.println("Producing value : " + i); 
              queue.add(i); 
              queue.notifyAll(); 
             } 
            } 
          } 
          }
        
         class Consumer extends Thread {
           private Queue<Integer> queue; 
           private int maxSize; 
           public Consumer(Queue<Integer> queue, int maxSize, String name){ 
                    super(name); this.queue = queue; this.maxSize = maxSize; 
           }
        
           public void run() { 
             while (true) { 
                      synchronized (queue) { 
                            while (queue.isEmpty()) { 
                                  try { 
                                        System.out .println("Queue is empty," +                         
                                 "Consumer thread is waiting" +
                                  " for producer thread to put something in queue");
                                  queue.wait(); 
                            } catch (Exception ex) { 
                                ex.printStackTrace();
                              }
                          } 
               System.out.println("Consuming value : " + queue.remove()); 
               queue.notifyAll(); 
              } 
            } 
          } 
          }
        

        这是消费者和生产者程序的示例。

        上述程序执行后的输出如下:

        How to use wait and notify 
        method in Java Solving Producer Consumper Problem 
        Queue is empty,Consumer thread is waiting for producer thread to put 
        something in queue 
        
        Producing value : -1692411980 
        Producing value : 285310787 
        Producing value : -1045894970 
        Producing value : 2140997307 
        Producing value : 1379699468 
        Producing value : 912077154 
        Producing value : -1635438928 
        Producing value : -500696499 
        Producing value : -1985700664 
        Producing value : 961945684 
        Queue is full, Producer thread waiting for consumer to take something from 
        queue Consuming value : -1692411980 
        Consuming value : 285310787 
        Consuming value : -1045894970 
         Consuming value : 2140997307 
        Consuming value : 1379699468 
        Consuming value : 912077154 
        Consuming value : -1635438928 
        Consuming value : -500696499 
        Consuming value : -1985700664 
        Consuming value : 961945684 
        Queue is empty,Consumer thread is waiting for producer thread to put 
        something  in queue 
        
        Producing value : 118213849
        

        所以,我们可以得出的结论是,notifyAll() 或 notify() 不会释放锁。看看输出,Producing value 和 Consuming value 没有交替打印,即分别打印。

        因此,notify/notifyAll 不会释放锁

        阅读更多:http://javarevisited.blogspot.com/2015/07/how-to-use-wait-notify-and-notifyall-in.html#ixzz57kdToLX6

        【讨论】:

          【解决方案6】:

          为了澄清我的理解并提供一个示例以显示何时释放锁,我在调用 notify()/NotifyAll() 之后在以下代码中添加了打印语句:

          class ThreadDemo {
              public static void main(String[] args) {
                  Shared s = new Shared();
                  new Producer(s).start();
                  new Consumer(s).start();
              }
          }
          
          class Shared {
              private char c = '\u0000';
              private boolean writeable = true;
          
              synchronized void setSharedChar(char c) {
                  while (!writeable)
                      try {
                          wait();
                      } catch (InterruptedException e) {
                      }
          
                  this.c = c;
                  writeable = false;
                  notifyAll();
                  System.out.println("setSharedChar notify() called - still in synchronized block.");
              }
          
              synchronized char getSharedChar() {
                  while (writeable)
                      try {
                          wait();
                      } catch (InterruptedException e) {
                      }
          
                  writeable = true;
                  notifyAll();
                  System.out.println("getSharedChar notify() called - still in synchronized block.");
          
                  return c;
              }
          }
          
          class Producer extends Thread {
              private Shared s;
          
              Producer(Shared s) {
                  this.s = s;
              }
          
              public void run() {
                  System.out.println("Starting producer thread.");
                  for (char ch = 'A'; ch <= 'Z'; ch++) {
                      System.out.println("Producer thread getting ready to create a char.");
                      try {
                          Thread.sleep((int) (Math.random() * 1000));
                      } catch (InterruptedException e) {
                      }
          
                      s.setSharedChar(ch);
                      System.out.println(ch + " produced by producer.");
                  }
              }
          }
          
          class Consumer extends Thread {
              private Shared s;
          
              Consumer(Shared s) {
                  this.s = s;
              }
          
              public void run() {
                  System.out.println("Starting consumer thread.");
                  char ch;
          
                  do {
                      System.out.println("Consumer thread getting ready to read a char.");
                      try {
                          Thread.sleep((int) (Math.random() * 1000));
                      } catch (InterruptedException e) {
                      }
          
                      ch = s.getSharedChar();
                      System.out.println(ch + " consumed by consumer.");
                  } while (ch != 'Z');
              }
          }
          

          当我运行这个例子足够多的时候,有一点我最终确实看到了程序显示的输出:

          ...
          F produced by producer.
          Producer thread getting ready to create a char.
          getSharedChar notify() called - still in synchronized block.
          F consumed by consumer.
          Consumer thread getting ready to read a char.
          setSharedChar notify() called - still in synchronized block.
          G produced by producer.
          Producer thread getting ready to create a char.
          getSharedChar notify() called - still in synchronized block.
          setSharedChar notify() called - still in synchronized block.
          G consumed by consumer.
          

          由于输出 getSharedChar 能够出现在 setSharedChar 之前,看来锁正在被立即释放,或者不需要通过调用 notifyAll() 重新进入同步的 getSharedChar() 函数。锁可能还在,但是如果没有它可以重新进入功能,有什么区别呢? 我能够看到用 notify() 代替 notifyAll() 的类似输出。这是在 64 位 Windows 7 系统上的 Java 1.7.0_15 上完成的。

          【讨论】:

            【解决方案7】:

            wait():几乎Java中的每个对象都有一个监视器,要进入任何同步块,线程必须首先获取这个监视器,然后只有他才能进入这个同步块。由于代码的关键部分一次由单个线程执行,因此它对应用程序的整体性能有很大影响。因此,代替持有资源(监视器)的线程可以被要求离开临界区并等待一段时间。为了实现这种行为,Java 直接在 Object 类中提供了一个 wait() api。

            因此,任何时候线程遇到 wait() API,它都会丢弃当前监视器以及它持有的所有其他监视器,并进入链接当前对象的等待状态。重要的是要理解,在线程首先获取监视器的对象的上下文中进入等待状态。在我解释的概念上,每个对象都有一个容器房屋,所有等待线程都存放在这里。 线程可以通过多种方式从这个 Object 的容器中出来。让我们看看..

            • 当另一个线程来并响铃一次时,在 Java 中通过调用 同一对象上的 notify() 方法。
            • 当另一个线程来并多次响铃时,其中一个线程 有机会从 Object 的集装箱房中出来。在 Java 中,我们可以 通过在同一个对象上调用 notifyAll() 来做到这一点。
            • 如果我们有 Thread 的引用在 container house 中等待。 在 Thread 对象上调用 interrupt() 会将其弹出等待状态 并为其带来 Object 的异常块。
            • 有重载的wait(long毫秒)和wait(long millSec, int nanos) 方法。由于线程的时间有资格来 退出等待状态并再次争夺对象监视器。如果 线程在超时后也无法获取监视器,那么它必须 仅等待 notify() 调用。

            notify():如果对象容器有多个处于等待状态的线程,那么在这个对象上调用 notify() 会给一个线程继续执行的机会。但是在退出等待状态后,线程仍然必须竞争对象监视器,如果成功获得监视器,它会继续执行,否则线程将返回等待状态。所以 notify() 也必须从同步块中调用。如果 notify() 不是从同步上下文中调用的,那么它会抛出 IllegalMonitorStateException。

            notifyAll(): 在 Object 上调用 notifyAll() 可以确保 Object 容器中的所有线程都被唤醒,但是一旦被唤醒,它们就必须相互竞争或者任何其他线程想要获取对象监视器。哪个线程成功继续执行,其他线程必须回到等待状态并在对象容器中安顿下来。与 notify() 一样,notifyAll() 也应该在同步上下文中调用。

            解释取自http://coder2design.com/thread-communication/

            【讨论】:

              【解决方案8】:

              在对象上调用notify() 方法会更改等待线程的状态。 通知线程仅在完成对要释放的锁对象的同步代码执行后才释放锁。

              所以它是这样的:

              等待() 如果一个线程在一个对象上调用wait() 方法,该线程会立即释放该对象的锁并进入等待状态。

              通知() 但是当一个线程在一个对象上调用notify() 方法时,如果线程还有一些工作要做(即在 notify() 调用之后执行的代码),线程不会立即释放该对象的锁。如果同步代码执行完毕或者notify()之后没有语句,则线程释放锁,让被唤醒的线程从等待状态中释放。

              【讨论】:

              • "在一个对象上调用 notify() 方法释放该对象的锁。" 及以后 "但是当一个线程调用对象上的 notify() 方法,线程可能不会立即释放该对象的锁”在这个答案中相互矛盾。
              猜你喜欢
              • 2011-04-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-06-04
              • 1970-01-01
              相关资源
              最近更新 更多