【问题标题】:Concept behind putting wait(),notify() methods in Object class [duplicate]将wait(),notify()方法放在Object类中的概念[重复]
【发布时间】:2013-07-24 07:20:33
【问题描述】:

我很难理解将wait() 放入Object 类背后的概念。对于这个问题,请考虑 wait()notifyAll() 是否在 Thread 类中。

class Reader extends Thread {
    Calculator c;
    public Reader(Calculator calc) {
        c = calc;
    }

    public void run() {
        synchronized(c) {                              //line 9
        try {
            System.out.println("Waiting for calculation...");
            c.wait();
        } catch (InterruptedException e) {}
            System.out.println("Total is: " + c.total);
        }
    }

    public static void main(String [] args) {
        Calculator calculator = new Calculator();
        new Reader(calculator).start();
        new Reader(calculator).start();
        new Reader(calculator).start();
        calculator.start();
    }
}

class Calculator extends Thread {
    int total;
    public void run() {
        synchronized(this) {                     //Line 31
            for(int i=0;i<100;i++) {
                total += i;
            }
             notifyAll();
        }
    } 
}

我的问题是它可以带来什么不同?在第 9 行中,我们正在获取对象 c 上的锁,然后执行等待,它满足等待条件,即我们需要在使用 wait 之前获取对象上的锁,因此在第 31 行获得了对 Calculator 对象的锁的 notifyAll 的情况.

【问题讨论】:

  • 很难理解你在问什么......
  • 我在问我们是否在 Thread 类中放置了等待和通知,然后我认为这段代码可能有效。
  • .wait().notify{,All}() 位于 Object,而不是 Thread。这就是允许在 JVM 中实现许多锁定原语的原因(SemaphoreCountDownLatch 等)
  • 这段代码并不真正适用于这个问题,因为ThreadObject 的子类,就像其他所有东西一样。您永远不会尝试在非Thread 对象上调用wait(),因此代码与您的问题无关。

标签: java multithreading wait notify


【解决方案1】:

我只是很难理解将 wait() 放在对象类中的概念对于这个问题,考虑一下 wait() 和 notifyAll() 是否在线程类中

在 Java 语言中,您 wait()Object 的特定实例上 - 准确地说是分配给该对象的监视器。如果您想向一个正在等待该特定对象实例的线程发送信号,那么您可以在该对象上调用notify()。如果您想向等待该对象实例的所有线程发送信号,请在该对象上使用notifyAll()

如果wait()notify()Thread 上,则每个线程都必须知道其他每个线程的状态。 thread1 如何知道 thread2 正在等待访问特定资源?如果 thread1 需要调用 thread2.notify(),它必须以某种方式发现 thread2 正在等待。线程需要某种机制来注册它们需要的资源或操作,以便其他人可以在东西准备好或可用时向它们发出信号。

在 Java 中,对象本身是线程之间共享的实体,允许它们相互通信。线程之间没有特定的知识,它们可以异步运行。它们运行并锁定、等待并通知他们想要访问的对象。他们不知道其他线程,也不需要知道他们的状态。他们不需要知道是 thread2 正在等待资源——他们只需通知资源,然后通知正在等待的任何人(如果有人的话)。

在 Java 中,我们将对象用作线程之间的同步、互斥和通信点。我们在一个对象上进行同步以获得对重要代码块的互斥访问并同步内存。如果我们在等待某个条件发生变化——某个资源变得可用,我们就在等待一个对象。如果我们想唤醒睡眠线程,我们会通知一个对象。

// locks should be final objects so the object instance we are synchronizing on,
// never changes
private final Object lock = new Object();
...
// ensure that the thread has a mutex lock on some key code
synchronized (lock) {
    ...
    // i need to wait for other threads to finish with some resource
    // this releases the lock and waits on the associated monitor
    lock.wait();
    ...
    // i need to signal another thread that some state has changed and they can
    // awake and continue to run
    lock.notify();
}

您的程序中可以有任意数量的锁定对象——每个锁定一个特定的资源或代码段。您可能有 100 个锁对象并且只有 4 个线程。当线程运行程序的各个部分时,它们可以独占访问其中一个锁对象。同样,他们不必知道其他线程的运行状态。

这使您可以根据需要随意增加或减少软件中运行的线程数。您发现 4 个线程在外部资源上阻塞太多,那么您可以增加数量。太用力地推动受虐服务器,然后减少正在运行的线程数。锁定对象可确保线程之间的互斥和通信,而与正在运行的线程数无关。

【讨论】:

  • 哇,不言自明,但正如你所指出的,我希望详细了解为什么我们从同步块调用 .Wait () ,因为它在等待状态下会释放其他人的锁,从而使资源可以访问其他线程。
  • 这只是@Sunny 规范的一部分。你需要有锁才能发送notify(),所以wait()必须先解锁它。
【解决方案2】:

简单来说,原因如下。

  1. Object 有监视器。
  2. 多个线程可以访问一个Object。对于synchronized 方法/块,一次只能有一个线程持有对象监视器。
  3. 位于Object 类中的wait(), notify() and notifyAll() 方法允许在该object 上创建的所有线程与其他线程进行通信
  4. 锁定(使用synchronized or Lock API)和通信(wait() and notify())是两个不同的概念。

如果Thread 类包含wait(), notify() and notifyAll() 方法,则会产生以下问题:

  1. Thread通讯问题
  2. Synchronization 在对象上是不可能的。如果每个线程都有监视器,我们将无法实现同步
  3. Inconsistency 处于对象状态

有关详细信息,请参阅此article

【讨论】:

  • 线程不是在“对象”上创建的。一个对象不存在多个线程。
  • 重新措辞。
【解决方案3】:

wait - wait 方法告诉当前线程放弃监视器并进入睡眠状态。

notify - 唤醒正在此对象的监视器上等待的单个线程。

所以你看到 wait() 和 notify() 方法在监视器级别工作,当前持有监视器的线程被要求通过 wait() 方法和通过 notify 方法(或 notifyAll)线程放弃该监视器,这些线程是在对象的监视器上等待被通知线程可以唤醒。

这里要注意的重要一点是,监视器被分配给一个对象而不是特定线程。这就是为什么这些方法在 Object 类中的原因之一。 重申线程在对象的监视器上等待(锁定),并且还会在对象上调用 notify() 以唤醒在对象的监视器上等待的线程。

【讨论】:

    【解决方案4】:

    Wait 和 notify 方法总是在对象上调用,所以它可能是 Thread 对象或简单对象(不扩展 Thread 类) 给定的示例将清除您的所有疑问。

    我在 ObjB 类上调用了 wait 和 notify,那是 Thread 类,所以我们可以说 wait 和 notify 是在任何对象上调用的。

    public class ThreadA {
        public static void main(String[] args){
            ObjB b = new ObjB();
            Threadc c = new Threadc(b); 
            ThreadD d = new ThreadD(b);
            d.setPriority(5);
            c.setPriority(1);
            d.start();
            c.start();
        }
    }
    
    class ObjB {
        int total;
        int count(){
            for(int i=0; i<100 ; i++){
                total += i;
            }
            return total;
        }}
    
    
    class Threadc extends Thread{
        ObjB b;
        Threadc(ObjB objB){
            b= objB;
        }
        int total;
        @Override
        public void run(){
            System.out.print("Thread C run method");
            synchronized(b){
                total = b.count();
                System.out.print("Thread C notified called ");
                b.notify();
            }
        }
    }
    
    class ThreadD extends Thread{
        ObjB b;
        ThreadD(ObjB objB){
            b= objB;
        }
        int total;
        @Override
        public void run(){
            System.out.print("Thread D run method");
            synchronized(b){
                System.out.println("Waiting for b to complete...");
                try {
                    b.wait();
                    System.out.print("Thread C B value is" + b.total);
                    } 
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
            }
        }
    }
    

    【讨论】:

      【解决方案5】:

      为了更好地理解为什么 wait() 和 notify() 方法属于 Object 类,我给你一个真实的例子: 假设加油站只有一个厕所,其钥匙保存在服务台。厕所是路过的驾车者的共享资源。要使用该共享资源,潜在用户必须获得马桶锁的钥匙。用户到服务台领取钥匙,打开门,从里面锁上门,然后使用设施。

      同时,如果第二个潜在用户到达加油站,他会发现厕所被锁住了,因此他无法使用。他去服务台,但钥匙不在那里,因为它在当前用户手中。当前用户完成后,他解锁门并将钥匙归还给服务台。他不为等待顾客而烦恼。服务台将钥匙交给等候的顾客。如果厕所被锁住时出现了不止一个潜在用户,他们必须排成一队等待锁的钥匙。每个线程都不知道谁在厕所。

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

      最后回答您的问题:锁可能是钥匙对象或服务台。它们都不是线程。

      但是,这些是当前决定马桶是锁定还是打开的对象。这些对象可以通知浴室已打开(“通知”)或要求人们在浴室锁定时等待。

      【讨论】:

      • 明确说明.....
      • 世界上的人们就像线程一样,他们使用共享资源,例如火车站等候大厅的椅子,加油站等。所有这些人都不知道谁在等待他们获得的这些释放资源。是资源宣布它们是免费和可用的,而不是人员,这就是为什么对象类有 wait() 和 notify() 方法。
      • 很好的解释,非常感谢。
      • 这里的班长是谁?钥匙或服务台?
      【解决方案6】:

      wait 和 notify 操作对隐式锁起作用,而隐式锁使线程间通信成为可能。并且所有对象都有自己的隐式对象副本。因此,保持等待并通知隐式锁存在的位置是一个不错的决定。

      或者,等待和通知也可以存在于 Thread 类中。我们可能不得不调用 Thread.getCurrentThread().wait() 而不是 wait(),与 notify 相同。 对于等待和通知操作,有两个必需参数,一个是等待或通知的线程,另一个是对象的隐式锁定。两者都可以在 Object 和 thread 类中使用。 Thread 类中的 wait() 方法与在 Object 类中所做的相同,将当前线程转换为等待状态,等待它最后获得的锁。

      所以是的,我认为等待和通知也可能存在于 Thread 类中,但它更像是一个将其保留在对象类中的设计决定。

      【讨论】:

        【解决方案7】:

        这只是我在这个问题上的 2 美分……不确定这是否完全正确。

        每个对象都有一个监视器和等待集 --> 线程集(这可能更多的是在操作系统级别)。这意味着监视器和等待集可以被视为对象的私有成员。在 Thread 类中拥有 wait() 和 notify() 方法意味着授予对 waitset 的公共访问权限或使用 get-set 方法来修改 waitset。你不想这样做,因为那是糟糕的设计。

        现在鉴于对象知道等待它的监视器的线程,它应该是对象的工作去唤醒那些等待它的线程,而不是一个线程类的对象去唤醒它们中的每一个(这只有在线程类对象被授予对等待集的访问权限时才有可能)。然而,去唤醒每个等待的线程并不是特定线程的工作。 (如果所有这些方法都在 Thread 类中,这正是会发生的情况)。它的工作只是释放锁并继续自己的任务。线程独立工作,不需要知道其他线程在等待对象监视器(对于线程类对象来说这是不必要的细节)。如果它开始自己唤醒每个线程......它正在远离其核心功能,那就是执行自己的任务。当您考虑可能有 1000 个线程的场景时,您可以假设它可以产生多大的性能影响。因此,鉴于对象类知道谁在等待它,它可以执行唤醒等待线程的工作,并且发送 notify() 的线程可以执行进一步的处理。

        打个比方(也许不是对的,但想不出别的)。当我们停电时,我们会打电话给该公司的客户代表,因为她知道合适的人来联系以解决问题。作为消费者,您不得知道背后的工程师是谁,即使您知道,也不可能给每个人打电话并告诉他们您的麻烦(这不是您的职责。您的职责是告知他们中断,CR 的工作是去通知(唤醒)合适的工程师)。

        如果这听起来正确,请告诉我......(我确实有时会混淆我的话)。

        【讨论】:

          【解决方案8】:

          这些方法适用于锁,并且锁与对象而不是线程相关联。因此,它在 Object 类中。

          wait()、notify() 和 notifyAll() 方法不仅仅是方法,它们是同步实用程序,用于 Java 中线程之间的通信机制。

          更详细的解释请访问:http://parameshk.blogspot.in/2013/11/why-wait-notify-and-notifyall-methods.html

          【讨论】:

            【解决方案9】:

            这个问题的其他答案都忽略了在Java中,有一个与每个对象相关联的互斥锁的关键点。 (我假设您知道什么是互斥锁或“锁”。)在大多数具有“锁”概念的编程语言中不是。例如,在 Ruby 中,您必须根据需要显式创建尽可能多的 Mutex 对象。

            我想我知道为什么 Java 的创建者会做出这个选择(尽管在我看来,这是一个错误)。原因与包含 synchronized 关键字有关。我相信 Java 的创建者(天真地)认为通过在语言中包含 synchronized 方法,人们编写正确的多线程代码会变得很容易——只需将所有共享状态封装在对象中,声明访问它的方法声明为synchronized,你就完成了!但结果不是这样……

            无论如何,由于任何类都可以有synchronized 方法,因此每个对象都需要一个互斥体,synchronized 方法可以锁定和解锁。

            waitnotify 都依赖于互斥体。也许你已经明白为什么会这样了......如果不是我可以添加更多解释,但现在,让我们说这两种方法都需要在互斥体上工作。每个 Java 对象都有一个互斥体,因此可以在任何 Java 对象上调用 waitnotify 是有意义的。这意味着它们需要被声明为Object的方法。

            另一种选择是将静态方法放在Thread 或其他东西上,它将任何Object 作为参数。这对于新的 Java 程序员来说不会那么混乱。但他们没有那样做。改变任何这些决定都为时已晚;太糟糕了!

            【讨论】:

            • 我的回答专门讨论了每个对象一个监视器。此外,在 Java 中,如果您愿意,还可以使用 ReentrantLock 或 JDK 中内置的其他锁定机制。
            • 好的,请注意,+1 包括这一点。确实,后来的 JRE 版本包含显式锁定对象,但从第一天起,隐式互斥锁就一直存在,这就是为什么 waitnotifyObject 上创建方法的原因。如果显式锁定对象,或者更好的是,条件队列对象包含在原始 JRE 中,那么 waitnotify 肯定会与它们相关联。
            【解决方案10】:

            wait() 方法将释放指定对象上的锁,并等待何时可以取回锁。

            notify()notifyAll() 会检查是否有线程在等待获取对象的锁,如果可能的话将把锁交给它们。

            锁之所以成为对象的一部分是因为资源(RAM)是由Object而不是Thread定义的。

            理解这个最简单的方法是线程可以共享对象(在例子中是所有线程共享的计算器),但对象不能共享属性(就像原语一样,即使引用自己对对象也不共享,它们只是指向相同的位置)。所以为了确保只有一个线程会修改一个对象,使用了同步锁定系统

            【讨论】:

            • 你的答案是混淆了锁和条件。它们不一样。 wait 释放锁并等待条件。通知释放一个(或多个线程)等待条件但不释放锁。
            【解决方案11】:

            第一个问题的答案是因为 java 中的每个对象只有一个 lock(monitor)wait(),notify(),notifyAll() 用于监视器共享,所以它们是 Object 类而不是 Threadclass 的一部分。

            【讨论】:

            • 是的,但如果等待是线程类的一部分,那么我认为他们也可以共享锁
            猜你喜欢
            • 2021-03-31
            • 2017-08-21
            • 2016-10-26
            • 1970-01-01
            • 2010-12-18
            • 2015-01-05
            • 1970-01-01
            • 2020-02-05
            • 1970-01-01
            相关资源
            最近更新 更多