Пользовательская синхронизация в Java с использованием встроенных блокировок, wait() и notify()
Источник: Medium Перед вами подробный пример реализации BlockingQueue (блокирующей очереди) с помощью блоков wait(), notify() и synchronized. Иногда на собеседованиях вас могут попросить привести пример использования wait() и notify(). Давайте разберем, как и где их нужно применять.wait() и notify(): что они делают и как их использовать
Как правило, wait() блокирует выполнение (очевидно, текущего потока) до тех пор, пока на том же мониторе не будет вызван соответствующий метод notify(). На практике это происходит внутри метода synchronized (где monitor — это класс, поэтому у вас может быть только один монитор) или блока synchronized, где вы сами решаете, какой монитор использовать.wait() и notify() с использованием методов synchronized
Вот пример, когда я вызываю метод synchronized, который находится в режиме ожидания до тех пор, пока я не вызову notify(), чтобы выполнение могло продолжаться.
public class WaitNotifyExampleSyncMethods {
synchronized void unlockAndContinue() {
notify();
}
synchronized void lockUntilNotified() throws InterruptedException {
System.out.println("Waiting...");
wait();
System.out.println("Notified. I can now continue");
}
public static void main(String[] args) throws InterruptedException {
WaitNotifyExampleSyncMethods waitNotifyExample = new WaitNotifyExampleSyncMethods();
new Thread(() -> {
try {
Thread.sleep(500);
waitNotifyExample.unlockAndContinue();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
waitNotifyExample.lockUntilNotified();
System.out.println("End of main method");
}
}
Вывод:
Waiting...
Notified. I can now continue
End of main method
wait() и notify() с использованием блоков synchronized
В случае, если я хочу использовать настраиваемый объект в качестве монитора (обычно это предпочтительнее, если вам нужно более одного в том же классе), то вот эквивалентный код, дающий один и тот же вывод. Обратите внимание, что monitor представляет собой простой объект Java, и я вызываю wait() и notify() для этого объекта из блоков synchronized, также используя тот же монитор/объект.
public class WaitNotifyExampleCustomMonitor {
public static void main(String[] args) throws InterruptedException {
Object monitor = new Object();
new Thread(() -> {
try {
Thread.sleep(500);
synchronized (monitor) {
monitor.notify(); // <-- (1)
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
synchronized (monitor) {
System.out.println("Waiting...");
monitor.wait(); // <-- (2)
System.out.println("Notified. I can now continue");
}
System.out.println("End of main method");
}
}
BlockingQueue (если вы не знали)
BlockingQueue (блокирующая очередь) — это очередь Queue, которая предназначена для реализации взаимодействия producer/consumer (поставщик/потребитель). BlockingQueue имеет дополнения:- Блокирующая операция take() — берет следующий элемент, если же очередь пустая, то блокирует выполнение до появления элемента (до момента, когда очередь перестанет быть пустой).
- Блокирующая операция put() — помещает элемент в очередь, если же очередь заполнена, то блокирует выполнение до освобождения места в очереди и успешного помещения в нее нового элемента.
Реализация блокирующей очереди BlockingQueue с использованием wait() и notify()
Ниже я вставлю небольшой пример кода, который содержит простые элементы LinkedList и два потока, которые я запускаю и жду их завершения в методе main:- Поток-производитель (producer thread) помещает в список 1000 элементов. Обратите внимание, что если очередь заполнена, то я вызываю wait() до тех пор, пока она не освободится. После того, как элемент добавлен, я вызываю его notify(), чтобы поток-потребитель (consumer thread), ожидающий освобождения элементов, знал, что уже есть что-то для потребления.
- Поток-потребитель работает в режиме демона (daemon mode) под while(true) и останавливается, если получает poison pill (это просто последний элемент). Как и в первом случае, если очередь пуста, то wait() вызывается до тех пор, пока ситуация не изменится. После того, как элемент использован, я вызываю его notify(), чтобы поток-производитель (возможно, ожидающий заполнения очереди) знал, что теперь есть свободное место для добавления хотя бы одного элемента.
import java.util.LinkedList;
import java.util.Random;
public class ProducerConsumerCustomSync {
LinkedList<Integer> queue = new LinkedList<>();
static final int LIMIT = 10;
Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
ProducerConsumerCustomSync pc = new ProducerConsumerCustomSync();
Thread producerThread = new Thread(() -> {
try {
pc.produce();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
Thread consumerThread = new Thread(() -> {
try {
pc.consume();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
producerThread.start();
consumerThread.start();
producerThread.join();
consumerThread.join();
System.out.println("end");
}
public void produce() throws InterruptedException {
int i = 0;
while (i < 1000) {
synchronized (lock) {
while (queue.size() == LIMIT) {
System.out.println("queue is full. wait for consumer to pop...");
lock.wait();
}
queue.add(i++);
if (i%100 == 0) {
Thread.sleep(new Random().nextInt(200));
}
lock.notify(); // разблокировать consumer, если он ожидает элементы
}
}
}
public void consume() throws InterruptedException {
while (true) {
synchronized (lock) {
while (queue.size() == 0) {
System.out.println("queue is empty. wait for producer to add items...");
lock.wait();
}
Integer pop = queue.removeFirst();
lock.notify(); // разблокировать producer, если он быть заполнен
System.out.printf("Taken %d from queue\n", pop);
if (pop == 999) {
System.out.printf("poison pill received !");
return;
}
}
}
}
}
Запуск вышеуказанного кода приводит к следующему:
queue is full. wait for consumer to pop...
Taken 0 from queue
...
Taken 9 from queue
queue is empty. wait for producer to add items...
queue is full. wait for consumer to pop...
Taken 10 from queue
...
Taken 18 from queue
Taken 19 from queue
queue is empty. wait for producer to add items...
queue is full. wait for consumer to pop...
Taken 20 from queue
...
Taken 985 from queue
Taken 986 from queue
queue is empty. wait for producer to add items...
queue is full. wait for consumer to pop...
Taken 987 from queue
Taken 988 from queue
...
Taken 996 from queue
queue is empty. wait for producer to add items...
Taken 997 from queue
Taken 998 from queue
Taken 999 from queue
poison pill received !end
Process finished with exit code 0
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ