55

我只是很难理解上课背后wait()的概念Objectwait()对于这个问题,考虑是否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 的情况.

4

11 回答 11

129

我只是很难理解将 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 个线程在外部资源上阻塞太多,那么您可以增加数量。太用力地推动受虐服务器,然后减少正在运行的线程数。锁定对象确保线程之间的互斥和通信,而与正在运行的线程数无关。

于 2013-07-24T17:59:02.803 回答
55

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

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

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

最后回答您的问题:锁可能是钥匙对象或服务台。其中没有一个是线程。

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

于 2015-09-03T16:44:34.050 回答
8

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

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

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

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

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

于 2013-07-24T21:17:30.263 回答
7

简单来说,原因如下。

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

如果Thread类包含wait(), notify() and notifyAll()方法,那么它将产生以下问题:

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

有关详细信息,请参阅本文

于 2016-07-11T11:17:10.053 回答
4

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

于 2013-07-24T17:02:49.230 回答
1

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

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

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

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

于 2017-05-07T18:01:49.580 回答
0

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

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

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

于 2013-11-20T21:14:52.813 回答
0

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

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

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

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

让我知道这听起来是否正确......(我有时确实有能力混淆我的话)。

于 2014-03-30T17:16:13.977 回答
0

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

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

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

于 2014-07-04T08:58:59.150 回答
-1

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

,将检查是否有线程等待获取对象的锁,如果可能,将其提供给它们notify()notifyAll()

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

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

于 2013-07-24T17:15:24.733 回答
-1

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();
                }
        }
    }
}
于 2016-09-25T18:33:50.493 回答