Потоки java wait notify

Потоки java wait notify

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

  • wait() : освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод notify()
  • notify() : продолжает работу потока, у которого ранее был вызван метод wait()
  • notifyAll() : возобновляет работу всех потоков, у которых ранее был вызван метод wait()

Все эти методы вызываются только из синхронизированного контекста — синхронизированного блока или метода.

Рассмотрим, как мы можем использовать эти методы. Возьмем стандартную задачу из прошлой темы — «Производитель-Потребитель» («Producer-Consumer»): пока производитель не произвел продукт, потребитель не может его купить. Пусть производитель должен произвести 5 товаров, соответственно потребитель должен их все купить. Но при этом одновременно на складе может находиться не более 3 товаров. Для решения этой задачи задействуем методы wait() и notify() :

public class Program < public static void main(String[] args) < Store store=new Store(); Producer producer = new Producer(store); Consumer consumer = new Consumer(store); new Thread(producer).start(); new Thread(consumer).start(); >> // Класс Магазин, хранящий произведенные товары class Store < private int product=0; public synchronized void get() < while (product<1) < try < wait(); >catch (InterruptedException e) < >> product--; System.out.println("Покупатель купил 1 товар"); System.out.println("Товаров на складе: " + product); notify(); > public synchronized void put() < while (product>=3) < try < wait(); >catch (InterruptedException e) < >> product++; System.out.println("Производитель добавил 1 товар"); System.out.println("Товаров на складе: " + product); notify(); > > // класс Производитель class Producer implements Runnable < Store store; Producer(Store store)< this.store=store; >public void run() < for (int i = 1; i < 6; i++) < store.put(); >> > // Класс Потребитель class Consumer implements Runnable < Store store; Consumer(Store store)< this.store=store; >public void run() < for (int i = 1; i < 6; i++) < store.get(); >> >

Итак, здесь определен класс магазина, потребителя и покупателя. Производитель в методе run() добавляет в объект Store с помощью его метода put() 6 товаров. Потребитель в методе run() в цикле обращается к методу get объекта Store для получения этих товаров. Оба метода Store — put и get являются синхронизированными.

Читайте также:  Зачем нужен interface php

Для отслеживания наличия товаров в классе Store проверяем значение переменной product . По умолчанию товара нет, поэтому переменная равна 0 . Метод get() — получение товара должен срабатывать только при наличии хотя бы одного товара. Поэтому в методе get проверяем, отсутствует ли товар:

Если товар отсутсвует, вызывается метод wait() . Этот метод освобождает монитор объекта Store и блокирует выполнение метода get, пока для этого же монитора не будет вызван метод notify() .

В методе put() работает похожая логика, только теперь метод put() должен срабатывать, если в магазине не более трех товаров. Поэтому в цикле проверяется наличие товара, и если товар уже есть, то освобождаем монитор с помощью wait() и ждем вызова notify() в методе get() .

И теперь программа покажет нам другие результаты:

Производитель добавил 1 товар Товаров на складе: 1 Производитель добавил 1 товар Товаров на складе: 2 Производитель добавил 1 товар Товаров на складе: 3 Покупатель купил 1 товар Товаров на складе: 2 Покупатель купил 1 товар Товаров на складе: 1 Покупатель купил 1 товар Товаров на складе: 0 Производитель добавил 1 товар Товаров на складе: 1 Производитель добавил 1 товар Товаров на складе: 2 Покупатель купил 1 товар Товаров на складе: 1 Покупатель купил 1 товар Товаров на складе: 0

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

А в методе put() с помощью wait() мы ожидаем освобождения места на складе. После того, как место освободится, добавляем товар и через notify() уведомляем покупателя о том, что он может забирать товар.

Источник

Знакомство с методами wait, notify, notifyAll

— Я познакомлю тебя с методами wait, notify, notifyAll класса Object.

Сегодня мы просто ознакомимся с ними, но потом еще раз вернемся и уже выделим на это больше времени.

— Эти методы были придуманы как часть механизма синхронизации нитей.

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

— Я помню, это делается с помощью ключевого слова synchronized.

— Правильно. Обычно такой код выглядит примерно так:

— Ага. Если две нити одновременно вызовут метод print(), то одна из них войдет в блок, помеченный synchronized, и заблокирует monitor, поэтому вторая нить будет ждать, пока монитор не освободится.

— Правильно. Как только нить входит в блок, помеченный synchronized, то объект-монитор помечается как занятый, и другие нити будут вынуждены ждать освобождения объекта-монитора. Один и тот же объект-монитор может использоваться в различных частях программы.

— Монитором принято называть объект, который хранит состояние занят/свободен.

Вот тут и вступают в дело методы wait и notify.

Собственно, методов как таковых всего два. Остальные – это лишь модификации этих методов.

Теперь разберемся, что же такое метод wait и зачем он нужен.

Иногда в программе может оказаться такая ситуация, что нить вошла в блок кода synchronized, заблокировала монитор и не может работать дальше, т.к. каких-то данных еще не хватает: например, файл который она должна обработать еще не загрузился или что-нибудь в таком духе.

Мы же можем просто подождать, когда файл скачается. Можно просто в цикле проверять – если файл еще не скачался – спать, например, секунду и опять проверять и т.д.

while(!file.isDownloaded()) < Thread.sleep(1000); >processFile(file);

Но в нашем случае такое ожидание слишком дорого. Т.к. наша нить заблокировала монитор, то другие нити вынуждены тоже ждать, хотя их данные для работы могут быть уже готовы.

Для решения этой проблемы и был придуман метод wait(). Вызов этого метода приводит к тому, что нить освобождает монитор и «становится на паузу».

Метод wait можно вызвать у объекта-монитора и только тогда, когда это монитор занят – т.е. внутри блока synchronized. При этом нить временно прекращает работу, а монитор освобождается, чтобы им могли воспользоваться другие нити.

Часто встречаются ситуации, когда в блок synchronized зашла нить, вызвала там wait, освободила монитор.

Затем туда вошла вторая нить и тоже стала на паузу, затем третья и так далее.

— А как же нить снимется с паузы?

— Для этого есть второй метод – notify.

Методы notify/notifyAll можно вызвать у объекта-монитора и только, когда этот монитор занят – т.е. внутри блока synchronized. Метод notifyAll снимает с паузы все нити, которые стали на паузу с помощью данного объекта-монитора.

Метод notify «размораживает» одну случайную нить, метод notifyAll – все «замороженные» нити данного монитора.

— Очень интересно, спасибо Риша.

— Рад, что тебе нравится, Амиго!

Есть еще модификации метода wait():

void wait(long timeout, int nanos)

Это, как еще говорят, wait с таймаутом. Метод работает как обычный wait, но если указанное время прошло, а нить никто не снял с паузы – она активируется сама.

Java-университет

Очень благодарен комментарием ниже, большинство информации находится именно в комментарии нежели чем в самой лекции.

Метод wait можно вызвать у объекта-монитора и только тогда, когда это монитор занят – т.е. внутри блока synchronized

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

 //— Я помню, это делается с помощью ключевого слова synchronized. 

Как работают методы wait() и notify()/notifyAll()? Эти методы определены у класса Object и предназначены для взаимодействия потоков между собой при межпоточной синхронизации. • wait(): освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод notify()/notifyAll(); • notify(): продолжает работу потока, у которого ранее был вызван метод wait(); • notifyAll(): возобновляет работу всех потоков, у которых ранее был вызван метод wait(). Когда вызван метод wait(), поток освобождает блокировку на объекте и переходит из состояния Работающий (Running) в состояние Ожидания (Waiting). Метод notify() подаёт сигнал одному из потоков, ожидающих на объекте, чтобы перейти в состояние Работоспособный (Runnable). При этом невозможно определить, какой из ожидающих потоков должен стать работоспособным. Метод notifyAll() заставляет все ожидающие потоки для объекта вернуться в состояние Работоспособный (Runnable). Если ни один поток не находится в ожидании на методе wait(), то при вызове notify() или notifyAll() ничего не происходит. Поток может вызвать методы wait() или notify() для определённого объекта, только если он в данный момент имеет блокировку на этот объект. wait(), notify() и notifyAll() должны вызываться только из синхронизированного кода.

Хочется дополнить лекцию не менее важной деталью. Допустим, у нас есть два потока: T1, T2. Есть один метод: doSomething(). Предположим, что он будет выглядеть вот таким образом:

 public synchronized void doSomething() throws InterruptedException < notify(); // Что-то делаем в этом методе. wait(); >

Запускаем два потока, которые в себе содержат только вызов метода doSomething(). Получается, что два потока обращаются к синхронизированному методу doSomething(). Кто успеет перехватить монитор — тот первый и войдет. Хорошо, какой-то поток из T1, T2 зайдет в метод. Другой же будет ожидать, когда тот освободит монитор. Допустим, первый зашедший поток в метод был T1. Он выполнит метод notify(). То есть, попытается разбудить поток, который отдавал свой монитор другому потоку. Но т.к таких нет, по сути никто и не проснется. Хорошо, движемся дальше. Поток T1 выполнит ЧТО-ТО и по итогу заснет (wait), отдав свой монитор другому потоку. Получается, что у потока T2 появилась возможность зайти в метод doSomething(). Он, не зевая, перехватывает монитор и заходит в данный метод. На первой строчке он пытается разбудить поток, который отдавал свой монитор. Поток T1 проснется, но свое выполнение он не продолжит. А всё потому, что монитор перехвачен потоком T2. Поэтому ему остается ждать, когда монитор снова будет освобожден. То есть, самая главная деталь — это то, что после вызова notify() пробудившийся поток не сразу продолжает свое выполнение, а сначала проверяет, свободен ли монитор. Если он занят, то поток будет дожидаться, когда монитор будет освобожден. В данном примере какой-то из поток T1, T2 вечно заснет, ожидая, когда кто-то его пробудит.

Источник

Оцените статью