Предположим у нас есть задача написать приложение Producer-Consumer.
Это приложение состоит из двух потоков — производителя, который создает данные, и потребителя, который что-то делает с этими данными.
Два потока обмениваются данными с использованием общего объекта.
При этом потребительский поток не должен пытаться извлекать данные до тех пор, пока поток производителя не создаст данные, а поток производителя не должен пытаться оставить новые данные, пока потребитель не извлечет старые данные.
Для создания такого приложения используется паттерн Защищенный блок.
Сначала значение empty установлено в true.
Поток потребителя вызывает синхронизированный метод take.
Объект класса блокируется.
Метод запускает цикл while.
В этом цикле вызывается метод wait.
При этом объект класса разблокируется.
Поток производитель вызывает метод put.
При этом объект класса блокируется.
Блок while метода put пропускается.
Записывается сообщение и значение empty устанавливается в false.
Вызывается метод notifyAll, который будит все потоки.
Монитор объекта перехватывается потоком потребителем.
Цикл while завершается, и значение empty устанавливается снова в true.
Сообщение потребляется.
Таким образом, паттерн защищенный блок обеспечивает координацию действий потоков.
При использовании этого паттерна не забудьте убедиться, что приложение имеет другие потоки, которые получают блокировки на объектах, на которых другие потоки ранее вызывали метод wait, и эти другие потоки вызывают методы notify или notifyAll, чтобы приложение могло избежать startvation для тех потоков, которые вызвали метод wait.
Метод notifyAll будит все ожидающие потоки, тогда как метод notify случайно выбирает один из ожидающих потоков и будит его.
Теперь вопрос.
Можно ли написать такой класс, чтобы состояние объекта такого класса не могло быть изменено после создания объекта, и таким образом, так как такой объект не может изменять состояние, он не может быть поврежден интерференцией потоков или быть в несогласованном состоянии.
Для этого нужно сделать следующее:
Не давать классу методы «setter» — методы, которые изменяют поля или объекты, на которые ссылаются поля.
Сделать все поля класса финальными и приватными.
Не допустить создание подклассов с помощью объявления класса финальным.
Не давать классу методы, изменяющие изменяемые объекты.
Создание объектов неизменяемыми, является хорошей практикой в некоторых случаях, и помогает создать потокобезопасный код.
Интерфейс Lock
Пакет java.util.concurrent представляет API более высокого уровня для синхронизации, координации и управления потоками.
В частности, это интерфейс Lock.
Обеспечивает управление доступом к общему ресурсу для нескольких потоков.
Основное отличие интерфейса Lock от использования низкоуровневого API в виде synchronized, wait, notify, и volatile, это:
Возможность запроса блокировки до тех пор, пока текущий поток не прервется. С простой синхронизацией невозможно прервать поток, который ожидает блокировки.
Запрашивать блокировку, если она свободна в течение заданного времени ожидания, и, если текущий поток не был прерван. С простой синхронизацией невозможно пытаться получить блокировку, не будучи готовым к долгому ожиданию.
Возможность одной блокировки иметь несколько условий, чтобы ждать или пробуждаться.
Так как в случае интерфейса Lock, блокировка представлена объектом, это дает возможность повторно используемой блокировки, а также возможность освобождать и приобретать блокировки в любом порядке, с простой синхронизацией можно освобождать блокировки только в том порядке, в котором они были приобретены.
Также, можно получить блокировку в одном методе, а освободить ее в другом методе, то есть получать блокировку неблочной структуры.
Это гибкость также может создать и проблему.
При использовании простой синхронизации невозможно забыть снять блокировку, JVM сделает это за вас, когда вы выйдете из блока synchronized.
Но при использовании интерфейса Lock можно забыть снять блокировку.
При этом ваша программа пройдёт все тесты и зависнет во время работы.
Класс ReentrantLock является наиболее используемой реализацией интерфейса Lock.
Этот класс добавляет дополнительную опцию справедливости.
Конструктор этого класса принимает необязательный параметр fairness.
Когда установлено true, при конкуренции нескольких потоков для получения блокировки, доступ предоставляется самому долго ожидающему потоку.
В противном случае блокировка не гарантирует какой-либо конкретный порядок доступа.
Простая синхронизация также не гарантирует какой-либо конкретный порядок получения блокировки для нескольких конкурирующих потоков.
В качестве примера, в случае простой синхронизации, у нас есть класс, имеющий синхронизированный блок, в котором производится декремент числа.
Как модифицировать этот код с использованием ReentrantLock.
Здесь мы создаем объект ReentrantLock, устанавливаем блокировку, выполняем операцию и производим разблокировку.
Метод newCondition класса ReentrantLock позволяет создавать несколько условий в одной блокировке для координации потоков.
Вильям Л Саймон , Вильям Саймон , Наталья Владимировна Макеева , Нора Робертс , Юрий Викторович Щербатых
Зарубежная компьютерная, околокомпьютерная литература / ОС и Сети, интернет / Короткие любовные романы / Психология / Прочая справочная литература / Образование и наука / Книги по IT / Словари и Энциклопедии