在Object类中放置wait(),notify()方法的概念

时间:2013-07-24 16:59:00

标签: java multithreading wait notify

我很难理解将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上的锁定,然后执行等待,以满足我们需要在使用等待之前获取对象的锁定的等待条件,因此对于notifyAll我们已经在第31行获取了对象的对象的锁定

11 个答案:

答案 0 :(得分:94)

  

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

答案 1 :(得分:37)

为了更好地理解为什么wait()和notify()方法属于Object类,我将给你一个真实的例子: 假设一个加油站有一个单独的厕所,其钥匙放在服务台。厕所是通过驾驶者的共享资源。为了使用该共享资源,预期用户必须获得上厕所锁的钥匙。用户前往服务台获取钥匙,打开门,从内部锁定并使用设施。

同时,如果第二个潜在用户到达加油站,他会发现厕所已锁定,因此无法使用。他去服务台,但钥匙不在那里,因为它掌握在当前用户手中。当前用户完成后,他解锁门并将钥匙返回服务台。他并不打算等待客户。服务台为等待的客户提供钥匙。如果在厕所被锁定时有多个预期用户出现,他们必须形成一个队列,等待锁的钥匙。每个帖子都不知道谁在厕所里。

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

最后你的问题的答案:锁可能是关键对象或服务台。这些都不是一个线程。

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

答案 2 :(得分:5)

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

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

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

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

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

答案 3 :(得分:4)

回答你的第一个问题是因为java中的每个对象只有一个lock(monitor)wait(),notify(),notifyAll()用于监视器共享,这就是为什么它们是Object类的一部分而不是{{1 }}类。

答案 4 :(得分:1)

简单来说,原因如下。

  1. Object有监听。
  2. 多个线程可以访问一个Object。对于synchronized方法/块,只有一个线程可以同时保存对象监视器。
  3. 位于wait(), notify() and notifyAll()类中的
  4. Object方法允许在object上创建的所有线程与其他
  5. 进行通信
  6. 锁定(使用synchronized or Lock API)和通信(wait() and notify())是两个不同的概念。
  7. 如果Thread类包含wait(), notify() and notifyAll()方法,则会产生以下问题:

    1. Thread沟通问题
    2. 对象上的
    3. Synchronization是不可能的。如果每个线程都有监视器,我们将无法实现同步
    4. Inconsistency处于对象状态
    5. 有关详细信息,请参阅此article

答案 5 :(得分:0)

这些方法适用于锁,而锁与Object相关联,而不是与Threads相关联。因此,它在Object类中。

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

有关详细说明,请访问: http://parameshk.blogspot.in/2013/11/why-wait-notify-and-notifyall-methods.html

答案 6 :(得分:0)

这只是我在这个问题上的2美分......不确定这是否完整。

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

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

进行类比(也许不是正确的,但不能想到其他任何东西)。当我们停电时,我们会致电该公司的客户代表,因为她知道有合适的人员来联系以解决问题。作为消费者,您不能知道谁是其背后的工程师,即使您知道,也不可能打电话给他们每个人并告诉他们您的麻烦(这不是您的责任。您的职责是告知他们有关停电和CR的工作是去通知(唤醒)合适的工程师。)

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

答案 7 :(得分:0)

等待并通知操作隐式锁定,隐式锁定使线程间通信成为可能。并且所有对象都有自己的隐式对象副本。所以保持等待并通知隐含锁定生活的地方是一个好的决定。

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

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

答案 8 :(得分:0)

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

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

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

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

答案 9 :(得分:-1)

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

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

锁是对象的一部分的原因是资源(RAM)由Object而不是Thread定义。

理解这一点最容易的方法是Threads可以共享Objects(在这个例子中是所有线程共享的计算器),但是对象不能共享Attributes(就像基元一样,甚至引用自己也不会共享对象,他们只是指向同一个地方)。因此,为了确保只有一个线程可以修改对象,使用同步锁定系统

答案 10 :(得分:-1)

等待并通知方法总是在对象上调用,所以它是可能是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();
                }
        }
    }
}