Как можно вызвать методы wait() и notify() для объектов, которые не являются потоками?

как wait() и notify() методы вызываются для объектов, которые не являются потоками? Это не имеет смысла, не так ли?

конечно, это должно иметь смысл, однако, потому что эти два метода доступны для всех объектов Java. Может кто-нибудь дать объяснение? У меня возникли проблемы с пониманием того, как общаться между потоками с помощью wait() и notify().

9 ответов


замки отличаются от нитей. Замок на структуру данных. Потоки-это вещи, обращающиеся к структуре данных. Блокировки находятся на объекте структуры данных, чтобы держать потоки от доступа к структуре данных небезопасным способом.

любой объект может использоваться в качестве встроенного замка (значение используется в сочетании с synchronized). Таким образом можно защитить доступ к любому объекту, добавив модификатор synchronized к методам, которые обращаются к общие данные. (Не то чтобы это хорошая идея, потому что это позволяет любому потоку, который может получить доступ к объекту, получить его блокировку, даже если он не вызывает никаких методов на нем; лучше сохранить блокировку в качестве частного члена структуры данных, чтобы доступ к ней был ограничен.)

wait и notify вызываются для объектов, которые используются в качестве блокировок. Блокировка является общей точкой связи:

  • когда поток, который имеет блокировку, вызывает notifyAll на нем другие потоки, ожидающие этой же блокировки, получают уведомление. Когда поток с блокировкой вызывает notify на нем один из потоков, ожидающих этой же блокировки, получает уведомление.

  • когда поток, который имеет блокировку, вызывает wait на нем поток освобождает блокировку и переходит в спящий режим, пока a) он не получит уведомление или b) он просто просыпается произвольно ("ложное пробуждение"); ожидающий поток остается застрявшим в вызове, чтобы ждать, пока он не проснется из-за одного из эти 2 причины, то поток должен повторно получить блокировку, прежде чем он сможет покинуть метод wait.

посмотреть Oracle учебник по охраняемым блокам, класс Drop-это общая структура данных, потоки, использующие Runnables производителя и потребителя, обращаются к ней. Блокировка объекта Drop управляет доступом потоков к данным объекта Drop.

потоки используются в качестве блокировок в реализации JVM, разработчикам приложений рекомендуется избегайте использования потоков в качестве блокировок. Например,документация для резьбы.присоединяйтесь говорит:

эта реализация использует цикл this.ожидание звонков обусловлено этим.потока isalive. Как нить завершает это.вызывается метод notifyAll. Рекомендуется, чтобы приложения не использовали wait, notify или notifyAll для экземпляров потока.


можно использовать 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();


вы можете остановить поток на время, как вы хотите, используя static 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. Я сори, если я неправильно понял ваш вопрос (английский-не мой родной)


подумайте, используя пример реальной жизни, a уборная. Когда вы хотите использовать туалет в своем офисе, у вас есть два варианта, чтобы убедиться, что никто больше не придет в туалет, как только вы его используете.

  1. заприте дверь туалета, чтобы все остальные знали, что он используется кем-то другим, когда они пытаются открыть дверь
  2. пойдите к каждому человеку в офисе, заприте их к их стульям (или столу, или что угодно), пойдите к уборная.

какой вариант вы бы взяли?

Да, это то же самое в Javaland!.

Итак, в приведенной выше истории,

  • туалет = объект, который вы хотите заблокировать (что только нужно использовать)
  • ваши коллеги по персоналу = другие темы, которые вы хотите сохранить

Так как и в реальной жизни, когда у вас есть собственный бизнес, вы блокируете этот объект. И когда вы закончите с этим объектом, вы отпускаете замок!.

(Да да! это очень простое описание того, что происходит. Конечно, реальная концепция немного отличается от этой, но это отправная точка)


в Java all Object реализует эти два метода, очевидно, если нет монитора, эти два метода бесполезны.


когда вы кладете какой-то код внутри блока synchronized:

 sychronized(lock){...}

поток, желающий выполнить все, что находится внутри этого блока, сначала получает блокировку объекта, и только один поток за раз может выполнить код, заблокированный на том же объекте. Любой объект может быть использован в качестве блокировки, но вы должны быть осторожны, чтобы выбрать объект, относящийся к области. Например, когда у вас есть несколько потоков, добавляющих что-то в учетную запись, и все они имеют некоторый код, ответственный за это внутри блок типа:

sychronized(this){...}

тогда синхронизация не происходит, потому что все они заблокированы на разных объектах. Вместо этого вы должны использовать объект account в качестве блокировки. Теперь рассмотрим, что эти потоки также имеют метод вывода средств со счета. В этом случае может возникнуть ситуация, когда поток, желающий снять что-то, сталкивается с пустой учетной записью. Он должен подождать, пока не появятся деньги, и отпустить блокировку другим потокам, чтобы избежать тупика. Вот что ждать и уведомлять методы для. В этом примере поток, который обнаруживает пустую учетную запись, освобождает блокировку и ждет сигнала от некоторого потока, который делает депозит:

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

когда другой поток депонирует некоторые деньги, он сигнализирует другим потокам, ожидающим той же блокировки. (конечно, код, ответственный за внесение депозитов и снятие средств, должен быть синхронизирован на одном замке, чтобы это работало и предотвращало повреждение данных).

balance += amountToDeposit;
lock.signallAll;

как вы видите методов wait и Notify имеет смысл только внутри синхронизированных блоков или методов.


  1. Wait and notify-это не просто обычные методы или утилита синхронизации, более того, они являются механизмом связи между двумя потоками в Java. И класс Object-правильное место, чтобы сделать их доступными для каждого объекта, если этот механизм недоступен через любое ключевое слово java, такое как synchronized. Помните, синхронизированы и ждать уведомления две разные области и не путать, что они одинаковы или связаны. Synchronized обеспечивает взаимное исключение и обеспечивает безопасность потока Java-класса, как состояние гонки, в то время как ожидание и уведомление-это механизм связи между двумя потоками.
  2. блокировки становятся доступными на основе каждого объекта, что является еще одной причиной wait и notify объявляется в классе Object, а не в классе Thread.
  3. в Java для ввода критического раздела кода потокам нужна блокировка, и они ждут блокировки, они не знают, какие потоки держат блокировку, вместо этого они просто знают, что блокировка удерживается каким-то потоком, и они должны ждать блокировки вместо того, чтобы знать, какой поток находится внутри синхронизированного блока и просить их отпустить блокировку. эта аналогия подходит для wait и notify, находящихся в классе объектов, а не в потоке в Java.

аналогия : поток Java-это пользователь, а туалет-это блок кода, который поток хочет выполнить. Java предоставляет способ блокировки кода для потока, который в настоящее время выполняет его с помощью synchorinized keywokd, и создания других потоков, которые хотят его использовать дождитесь завершения первого потока. Эти другие потоки помещаются в состояние ожидания. Java не так справедлива, как станция обслуживания, потому что нет очереди для ожидающих потоков. Любой из ожидающих потоков может получить следующий монитор, независимо от того, в каком порядке они его запросили. Единственная гарантия заключается в том, что все потоки рано или поздно будут использовать отслеживаемый код.

источник

если вы посмотрите на следующих производителя и потребителя код:
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);
        }
    }
}

источник


" этот метод должен вызываться только потоком, который является владельцем монитора этого объекта." Поэтому я думаю, что вы должны убедиться, что есть поток, который является монитором на объекте.


на самом деле wait, notify функция-член не должна принадлежать потоку, вещь она должна принадлежать имени как переменной условие который исходит от поток posix . И вы можете посмотреть, как cpp обернуть эту концепцию, она обернет ее в выделенное имя класса std:: condition_variable.

Я думаю, что cpp делает инкапсуляцию лучше, чем java, java делает это слишком много, он помещает концепцию в класс объектов напрямую, Ну, что делает людей смущение в начале.