如何在非线程的对象上调用wait()和notify()方法?

时间:2013-04-24 16:17:43

标签: java multithreading wait notify

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

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

10 个答案:

答案 0 :(得分:33)

锁定是关于保护共享数据。

锁定在受保护的数据结构上。线程是访问数据结构的东西。锁定在数据结构对象上,以防止线程以不安全的方式访问数据结构。

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

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

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

  • 当一个有锁的线程调用wait时,该线程会释放锁并进入休眠状态,直到a)它收到通知,或b)它只是任意唤醒(“虚假唤醒“);由于这两个原因之一,等待线程仍然停留在调用等待直到它被唤醒,然后线程必须重新获取锁定才能退出等待方法。

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

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

  

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

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

答案 1 :(得分:23)

您可以使用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();

答案 2 :(得分:5)

您可以根据需要使用静态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();
    }
}

}

P.S。如果我错了理解你的问题(英语不是我的母语),我就是太太了。

答案 3 :(得分:4)

当你在synchronized块中放入一些代码时:

 sychronized(lock){...}

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

sychronized(this){...}

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

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

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

balance += amountToDeposit;
lock.signallAll;

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

答案 4 :(得分:4)

使用现实生活中的例子,洗手间。当您想在办公室使用洗手间时,您有两种方法可以确保一旦您使用洗手间,就不会有其他人来洗手间。

  1. 锁上洗手间的门,这样其他人就会知道当他们试图打开门时,别人会使用它
  2. 去办公室里的每个人,把它们锁在椅子上(或桌子上,或其他什么地方),然后去洗手间。
  3. 你会选择哪个选项?

    是的,它在Javaland中是一样的!

    所以在上面的故事中,

    • Washroom =您要锁定的对象(只需要使用)
    • 您的员工同事=您想要保留的其他主题

    就像在现实生活中一样,当你有一些私人生意时,你会锁定那个对象。当你完成那个对象时,你放开了锁!。

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

答案 5 :(得分:3)

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

答案 6 :(得分:2)

  1. 等待和通知不仅仅是普通方法或同步实用程序,而是它们是Java中两个线程之间的通信机制。如果通过任何java关键字(例如synchronized)无法使用此机制,那么Object类是使它们可用于每个对象的正确位置。记住同步和等待通知是两个不同的区域,不要混淆它们是相同的或相关的。同步是提供互斥并确保Java类的线程安全,如等待和通知的竞争条件是两个线程之间的通信机制。
  2. 锁是基于每个对象提供的,这是在Object类而不是Thread类中声明wait和notify的另一个原因。
  3. 在Java中为了进入代码的关键部分,线程需要锁定并等待锁定,他们不知道哪些线程持有锁而不知道锁定是否被某个线程保持并且他们应该等待锁定而不是锁定知道哪个线程在同步块内并要求它们释放锁定。这个类比适合于等待并通知对象类而不是Java中的线程。
  4. 类比: Java线程是用户,厕所是线程希望执行的代码块。 Java提供了一种方法来锁定当前使用synchorinized 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

答案 7 :(得分:2)

实际上,waitnotify成员函数不应该属于线程,它应该属于来自posix thread条件变量。您可以看一下cpp如何包装此概念,并将其包装到专用的类名std::condition_variable中。

我认为cpp的封装效果比java更好,java这样做的太多,它将概念直接放入Object类中,一开始就让人感到困惑。

答案 8 :(得分:1)

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

答案 9 :(得分:0)

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