【问题标题】:How can the wait() and notify() methods be called on Objects that are not threads?如何在不是线程的对象上调用 wait() 和 notify() 方法?
【发布时间】:2013-04-18 07:23:16
【问题描述】:

如何在不是线程的对象上调用wait() 和notify() 方法?这真的没有意义,不是吗?

当然,这一定是有意义的,因为这两种方法可用于所有 Java 对象。有人可以提供解释吗?我无法理解如何使用 wait() 和 notify() 在线程之间进行通信。

【问题讨论】:

  • 任何Object都可以用作监视器Object,因此Object类Object实现了这些方法。
  • 我认为他们在Object 中的存在更像是一个“标记”,Thread 扩展了对象
  • wait()、notify() 和 notifyAll() 仅在从同步方法或同步块中调用时才有效。

标签: java multithreading wait notify


【解决方案1】:

锁定是为了保护共享数据。

锁在被保护的数据结构上。线程是访问数据结构的事物。锁在数据结构对象上,以防止线程以不安全的方式访问数据结构。

任何对象都可以用作内在锁(意思是与synchronized结合使用)。这样,您可以通过将同步修饰符添加到访问共享数据的方法来保护对任何对象的访问。

在用作锁的对象上调用wait 和notify 方法。锁是一个共享的通信点:

  • 当一个拥有锁的线程在其上调用notifyAll 时,等待同一个锁的其他线程会收到通知。当一个有锁的线程调用notify 时,等待同一个锁的线程之一会得到通知。

  • 1234563 ");由于这两个原因之一,等待线程一直停留在调用中等待直到它被唤醒,然后线程必须重新获取锁才能退出等待方法。

参见Oracle tutorial on guarded blocks,Drop 类是共享数据结构,使用 Producer 和 Consumer 可运行对象的线程正在访问它。锁定 Drop 对象控制线程如何访问 Drop 对象的数据。

在 JVM 实现中线程被用作锁,建议应用程序开发人员避免将线程用作锁。例如,documentation for Thread.join 表示:

此实现使用以 this.isAlive 为条件的 this.wait 调用循环。当线程终止时,将调用 this.notifyAll 方法。建议应用程序不要在 Thread 实例上使用 wait、notify 或 notifyAll。

Java 5 引入了实现java.util.concurrent.locks.Lock 的显式锁。这些比隐式锁更灵活;有类似于等待和通知的方法(等待和信号),但它们是在条件上,而不是在锁上。拥有多个条件可以只针对那些等待特定类型通知的线程。

【讨论】:

  • 感谢您的解释,我有一个问题,为什么设计类似于 wait、notify 和 notifyAll 是每个类都可用的方法,因为每个类都有父类作为对象类,为什么不喜欢接口是可克隆接口的方式,我们需要覆盖克隆方法吗?
  • @Rahul:不知道,但请记住,Java 最初是为小型设备上的移动代码而设计的。线程应该很容易,但他们并没有考虑高度并发的服务器应用程序。
  • @NathanHughes "这不是一个好主意,因为这允许任何可以访问对象的线程获取它的锁,即使它没有调用它的任何方法;最好将锁保持为数据结构的私有成员被锁定,因此对其的访问受到限制。” 请说清楚。
  • @abksrv:有一个单独的问题专门解决了这个问题;看看stackoverflow/q/442564是否更清晰
  • @NathanHughes 好像链接坏了!
【解决方案2】:

您可以使用wait() 和notify() 来同步您的逻辑。举个例子

synchronized (lock) {
    lock.wait(); // Will block until lock.notify() is called on another thread.
}

// Somewhere else...
...
synchronized (lock) {
    lock.notify(); // Will wake up lock.wait()
}

lock 是类成员 Object lock = new Object();

【讨论】:

【解决方案3】:

想想现实生活中的例子,一个洗手间。当您想在办公室使用洗手间时,您有两种选择,以确保在您使用洗手间后没有其他人会来。

  1. 锁上洗手间门,这样其他人在试图打开门时都会知道它是被其他人使用的
  2. 去找办公室里的每个人,把他们锁在椅子(或桌子,或其他任何东西)上,去洗手间。

你会选择哪个?

是的,Javaland 也一样!

所以在上面的故事中,

  • 洗手间 = 您要锁定的对象(只有您需要使用)
  • 您的员工同事 = 其他您想拒之门外的话题

就像在现实生活中一样,当您有一些私人业务时,您会锁定该对象。当你处理完那个对象后,你就松开了锁!

(是的,是的!这是对发生的事情的一个非常简单的描述。当然真正的概念与此略有不同,但这是一个起点)

【讨论】:

    【解决方案4】:

    您可以使用静态Thread 类方法sleep() 随时停止线程。

    public class Main {
        //some code here
    
        //Thre thread will sleep for 5sec.
        Thread.sleep(5000);   
    }
    

    如果您想停止某些对象,您需要在 syncronized 块内调用此方法。

    public class Main {
    
    //some code
    
    public void waitObject(Object object) throws InterruptedException {
        synchronized(object) {
            object.wait();
        }
    }
    
    public void notifyObject(Object object) throws InterruptedException {
        synchronized(object) {
            object.notify();
        }
    }
    

    }

    附:如果我理解错了你的问题,我很抱歉(英语不是我的母语)

    【讨论】:

    【解决方案5】:

    当你把一些代码放在同步块中时:

     sychronized(lock){...}
    

    想要执行此块内的任何内容的线程首先获取对象上的锁,并且一次只有一个线程可以执行锁定在同一对象上的代码。任何对象都可以用作锁,但您应该小心选择与范围相关的对象。例如,当您有多个线程向帐户添加某些内容时,它们都有一些代码负责在一个块中,例如:

    sychronized(this){...}
    

    然后不会发生同步,因为它们都锁定在不同的对象上。相反,您应该使用帐户对象作为锁。 现在考虑这些线程也有从帐户中提取的方法。在这种情况下,可能会出现想取款的线程遇到空帐户的情况。它应该等到有钱后再将锁释放给其他线程以避免死锁。这就是等待和通知方法的用途。在此示例中,遇到空帐户的线程会释放锁定并等待来自进行存款的某个线程的信号:

    while(balance < amountToWithdraw){
        lock.wait();
    }
    

    当其他线程存入一些钱时,它会向其他线程发出信号,等待同一个锁。 (当然,负责存款和取款的代码必须在同一个锁上同步才能正常工作并防止数据损坏)。

    balance += amountToDeposit;
    lock.signallAll;
    

    正如您所见,方法等待和通知仅在同步块或方法中才有意义。

    【讨论】:

      【解决方案6】:

      在Java中所有的Object都实现了这两个方法,显然如果没有监视器这两个方法是没用的。

      【讨论】:

        【解决方案7】:

        其实wait、notify成员函数不应该属于线程,它应该属于的东西命名为条件变量,来自posix thread。你可以看看 cpp 是如何包装它的,它把它包装成一个专用的类std::condition_variable。

        Java 没有做这种封装,而是以更高级的方式包装条件变量:monitor(将功能直接放入 Object 类中)。

        如果你不知道监视器或条件变量,好吧,那确实会让人一开始就感到困惑。

        【讨论】:

          【解决方案8】:
          1. Wait 和 notify 不仅仅是普通的方法或同步实用程序,它们是 Java 中两个线程之间的通信机制。如果此机制不能通过任何 java 关键字(如同步)使用,则 Object 类是使它们可用于每个对象的正确位置。记住同步和等待通知是两个不同的领域,不要混淆它们是相同的还是相关的。同步是提供互斥并确保 Java 类的线程安全,例如竞争条件,而等待和通知是两个线程之间的通信机制。
          2. 锁是基于每个 Object 提供的,这是在 Object 类而不是 Thread 类中声明 wait 和 notify 的另一个原因。
          3. 在 Java 中,为了进入代码的关键部分,线程需要锁定并且它们等待锁定,它们不知道哪些线程持有锁定,而是只知道锁定被某个线程持有,它们应该等待锁定知道哪个线程在同步块内并要求他们释放锁。这个类比适合等待和通知在对象类上而不是在 Java 中的线程上。

          类比: Java 线程是用户,厕所是线程希望执行的代码块。 Java 提供了一种方法来锁定当前正在执行的线程的代码,该线程使用同步化的 keywokd,并让希望使用它的其他线程等到第一个线程完成。这些其他线程被置于等待状态。 Java 不像服务站那样公平,因为没有等待线程的队列。任何一个等待的线程都可以下一个得到监视器,而不管它们请求的顺序如何。唯一的保证是所有线程迟早都会使用被监控的代码。

          Source

          如果您查看以下生产者和消费者代码:
          sharedQueue 对象在 producer and consumer 线程之间进行线程间通信。

          import java.util.Vector;
          import java.util.logging.Level;
          import java.util.logging.Logger;
          
          public class ProducerConsumerSolution {
          
              public static void main(String args[]) {
                  Vector<Integer> sharedQueue = new Vector<Integer>();
                  int size = 4;
                  Thread prodThread = new Thread(new Producer(sharedQueue, size), "Producer");
                  Thread consThread = new Thread(new Consumer(sharedQueue, size), "Consumer");
                  prodThread.start();
                  consThread.start();
              }
          }
          
          class Producer implements Runnable {
          
              private final Vector<Integer> sharedQueue;
              private final int SIZE;
          
              public Producer(Vector<Integer> sharedQueue, int size) {
                  this.sharedQueue = sharedQueue;
                  this.SIZE = size;
              }
          
              @Override
              public void run() {
                  for (int i = 0; i < 7; i++) {
                      System.out.println("Produced: " + i);
                      try {
                          produce(i);
                      } catch (InterruptedException ex) {
                          Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
                      }
          
                  }
              }
          
              private void produce(int i) throws InterruptedException {
          
                  // wait if queue is full
                  while (sharedQueue.size() == SIZE) {
                      synchronized (sharedQueue) {
                          System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: "
                                  + sharedQueue.size());
          
                          sharedQueue.wait();
                      }
                  }
          
                  // producing element and notify consumers
                  synchronized (sharedQueue) {
                      sharedQueue.add(i);
                      sharedQueue.notifyAll();
                  }
              }
          }
          
          class Consumer implements Runnable {
          
              private final Vector<Integer> sharedQueue;
              private final int SIZE;
          
              public Consumer(Vector<Integer> sharedQueue, int size) {
                  this.sharedQueue = sharedQueue;
                  this.SIZE = size;
              }
          
              @Override
              public void run() {
                  while (true) {
                      try {
                          System.out.println("Consumed: " + consume());
                          Thread.sleep(50);
                      } catch (InterruptedException ex) {
                          Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
                      }
          
                  }
              }
          
              private int consume() throws InterruptedException {
                  //wait if queue is empty
                  while (sharedQueue.isEmpty()) {
                      synchronized (sharedQueue) {
                          System.out.println("Queue is empty " + Thread.currentThread().getName()
                                              + " is waiting , size: " + sharedQueue.size());
          
                          sharedQueue.wait();
                      }
                  }
          
                  //Otherwise consume element and notify waiting producer
                  synchronized (sharedQueue) {
                      sharedQueue.notifyAll();
                      return (Integer) sharedQueue.remove(0);
                  }
              }
          }
          

          Source

          【讨论】:

            【解决方案9】:

            “这个方法只能被这个对象的监视器的所有者线程调用。” 所以我认为你必须确保有一个线程是对象的监视器。

            【讨论】:

              【解决方案10】:

              对象类是为每个对象提供锁的正确位置。 假设有一个联合银行账户,因此多个用户可以使用同一个账户通过多个渠道进行交易。目前,该账户的余额为 1500/-,账户中剩余的最低余额为 1000/-。现在,第一个用户试图通过 ATM 提取 500 美元,而另一个用户试图通过刷卡机购买任何价值 500 美元的商品。这里无论哪个通道首先访问该帐户执行事务,都会首先获得该帐户的锁,另一个通道将等待事务完成并释放该帐户的锁,因为无法知道哪个通道已经获得锁以及哪个通道正在等待获取锁。因此锁定总是应用于帐户本身而不是通道。在这里,我们可以将账户视为一个对象,将频道视为一个线程。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多