Что такое Java-монитор

Обновлено: 21.11.2024

Язык Java и система среды выполнения поддерживают синхронизацию потоков с помощью мониторов, которые впервые были описаны в статье К.А. Хора Мониторы: концепция структурирования операционной системы (Communications of the ACM, 17(10), 549-557, 1974). Монитор связан с определенным элементом данных (переменной условия) и функционирует как блокировка этих данных. Когда поток удерживает монитор для некоторого элемента данных, другие потоки блокируются и не могут проверять или изменять данные.

Сегменты кода в программе, которые обращаются к одним и тем же данным из отдельных параллельных потоков, называются критическими секциями. В языке Java вы помечаете критические разделы в своей программе с помощью ключевого слова synchronized.

Примечание. Как правило, критические разделы в программах Java являются методами. Вы можете пометить меньшие сегменты кода как синхронизированные. Однако это нарушает парадигмы объектно-ориентированного программирования и приводит к запутанному коду, который трудно отлаживать и поддерживать. Для большинства целей программирования на Java лучше всего использовать синхронизацию только на уровне метода.

В языке Java уникальный монитор связан с каждым объектом, у которого есть синхронизированный метод. Класс CubbyHole для примера производителя/потребителя, представленный в предыдущем разделе, имеет два синхронизированных метода: метод put, который используется для изменения значения в CubbyHole, и метод get, который используется для получения текущего значения. Таким образом, система связывает уникальный монитор с каждым экземпляром CubbyHole.

Вот исходный код объекта CubbyHole. Элементы кода, выделенные жирным шрифтом, обеспечивают синхронизацию потоков:

У CubbyHole есть две закрытые переменные:contents, которая является текущим содержимым CubbyHole, и логическая доступная переменная, которая указывает, можно ли получить содержимое CubbyHole. Когда available равно true, производитель только что поместил новое значение в CubbyHole, а потребитель еще не использовал его. Потребитель может использовать значение в CubbyHole, только если для параметра available установлено значение true.

Поскольку CubbyHole имеет синхронизированные методы, Java предоставляет уникальный монитор для каждого экземпляра CubbyHole (включая тот, который совместно используется производителем и потребителем). Всякий раз, когда управление входит в синхронизированный метод, поток, вызвавший метод, получает монитор для объекта, чей метод был вызван. Другие потоки не могут вызывать синхронизированный метод для того же объекта, пока монитор не будет освобожден.

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

Всякий раз, когда Производитель вызывает метод put CubbyHole, Производитель получает монитор для CubbyHole, тем самым предотвращая вызов Потребителем метода get CubbyHole. (Метод ожидания временно освобождает монитор, как вы увидите позже.)

И наоборот, всякий раз, когда Потребитель вызывает метод get CubbyHole, Потребитель получает монитор для CubbyHole, тем самым не позволяя Производителю вызывать метод put.

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

Попробуйте следующее: удалите строки, выделенные жирным шрифтом, в листинге класса CubbyHole, показанном выше. Перекомпилируйте программу и запустите ее снова. Что случилось? Поскольку не было предпринято никаких явных усилий для синхронизации потоков производителя и потребителя, потребитель потребляет с опрометчивой энергией и получает целую кучу нулей вместо того, чтобы получать каждое целое число от 0 до 9 ровно один раз.

Синхронизация потоков с мониторами Java

Мы можем избежать этой ситуации, объявив метод Increment синхронизируемым, как в пересмотренном сценарии:

Когда поток 2 пытается выполнить метод Increment() для того же объекта счетчика, поток блокируется. Поток 2 не может получить право собственности на монитор объекта счетчика; монитор уже принадлежит потоку 1. Поток 2 приостанавливается до тех пор, пока монитор не станет доступным. Когда поток 1 освобождает монитор, поток 2 получает доступ к монитору и продолжает работу, завершая вызов метода.

Обратите внимание, что мониторы Java не похожи на традиционные критические разделы. Объявление метода синхронизированным не означает, что только один поток может выполнять этот метод одновременно, как в случае с критической секцией. Это означает, что только один поток может вызывать этот метод (или любой синхронизированный метод) для конкретного объекта в любой момент времени. Мониторы Java связаны с объектами, а не с блоками кода. Два потока могут одновременно выполнять один и тот же синхронизированный метод при условии, что метод вызывается для разных объектов (то есть a.method() и b.метод() , где а != б ).

Упражнение 1 . Загрузите код PC.java для задачи производитель-потребитель. В коде используются синхронизированные методы доступа Get и Put к общему буферу. Скомпилируйте и запустите код. Что случилось? Почему это происходит?

Теперь вам нужно завершить запущенный процесс. Откройте новое окно telnet и введите в командной строке следующие команды: где your_account_name — ваше имя для входа. Первая команда выводит список всех процессов, запущенных на созданной вами машине Unix. Определите процесс ПК и используйте его идентификационный номер pid в команде kill.

Координация потоков в Java

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

Мы действительно хотим, чтобы источник освобождал монитор, если буфер заполнялся, и позволял потребителю продолжить работу. Точно так же Потребитель должен освободить монитор, если буфер опустеет, и позволить Производителю продолжить работу. Для координации двух потоков мы должны использовать методы объекта wait() и notify или notifyAll().

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

Метод notifyAll() пробуждает все потоки, ожидающие обработки рассматриваемого объекта. Пробужденные потоки конкурируют за монитор. Один поток получает его, а остальные возвращаются к ожиданию.

Метод notify() произвольно пробуждает один из потоков, ожидающих обработки рассматриваемого объекта. Правильная реализация буфера для задачи производитель-потребитель приведена ниже.

Важное примечание. Поток может вызывать wait() , notify() или notifyAll() для объекта, только если он владеет монитор этого объекта.

<ПР> Если класс имеет один или несколько синхронизированных методов, каждый объект класса получает очередь, содержащую все потоки, ожидающие выполнения одного из синхронизированных методов.

<ПР> Если два или более потока модифицируют объект, объявите методы, выполняющие модификацию, как synchronized.

Упражнение 2 . Внесите описанные выше изменения в код производителя-потребителя из упражнения 2 и перекомпилируйте его. На этот раз он должен работать успешно.

Что такое монитор в параллельном программировании на Java?

Когда я прочитал, что "каждый объект связан с монитором", что это значит?

Это особый объект?

8 ответов 8

Монитор — это механизм управления одновременным доступом к объекту.

Это позволяет вам делать следующее:

Это предотвращает одновременный доступ потоков 1 и 2 к отслеживаемому (синхронизированному) разделу. Один запустится, а монитор предотвратит доступ другого к региону до завершения первого.

Это не особый объект. Механизм синхронизации размещен в корне иерархии классов: java.lang.Object .

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

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

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

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

@Andrzej: Итак, с каждым объектом связан ОДИН монитор. Тогда у меня может быть много синхронизированных методов. После того, как каждый из этих методов вызывает поток, он получает этот монитор, который выполняет синхронизацию.

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

Монитор — это сущность, которая имеет как блокировку, так и набор ожидания.В Java любой объект может служить монитором.

Для подробного объяснения того, как мониторы работают в Java, я рекомендую прочитать раздел Monitor Mechanics книги Concurrent Programming in Java (предыдущая ссылка показывает предварительный просмотр в книгах Google, и этот раздел доступен для чтение).

Как вы сказали: "Монитор — это сущность ..", означает ли это, что монитор – это внутренний объект/состояние, которое обладает/отслеживает блокировку и набор ожидания? Если нет, не могли бы вы уточнить сущность здесь? В основном, когда мы java doc говорит, что пробуждает все потоки, ожидающие на мониторе этого объекта. о notifyall(), я получаю, что объект поддерживает (с помощью внутреннего объекта/объекта), что все потоки ждут блокировки, этот внутренний объект/объект называется монитором?

  1. Монитор — это концепция/механизм, который не ограничивается языком Java.
  2. "В параллельном программировании монитор — это объект или модуль, предназначенный для безопасного использования более чем одним потоком";
  3. Как известно каждому читателю, каждый объект в Java является подклассом java.lang.Object. Разработчики Java создали java.lang.Object таким образом, чтобы он обладал функциями и характеристиками, позволяющими Java-программистам использовать любой объект в качестве монитора. Например, у каждого объекта есть очередь ожидания, очередь повторного входа и методы ожидания и уведомления, делающие его монитором;
  4. о мониторах читайте здесь.

В параллельном программировании нам нужно сосредоточиться на двух вещах

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

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

Мониторы используются для взаимного исключения и синхронизации.

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

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

Эта важная область защищена замком, и этот замок обеспечивает взаимное исключение.

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

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

Как достигается взаимное исключение в Monitor?

Каждый объект связан с монитором, и этот монитор имеет блокировку, где каждый поток может блокировать или разблокировать объект с помощью этой блокировки при доступе к общим переменным. В явном виде это означает, что только один поток одновременно может удерживать блокировку на мониторе. Любые другие потоки, пытающиеся заблокировать эту блокировку, блокируются до тех пор, пока не смогут получить блокировку. когда новый поток пытается получить блокировку, и если поток уже владеет блокировкой, тогда этот поток будет ожидать набора записей для получения блокировки. когда поток, получивший блокировку, завершит свою критическую секцию, он освободит блокировку. Таким образом, следующий поток получит блокировку, но этот следующий поток берется из набора записей и будет определяться JVM на основе некоторых критериев, таких как FIFO.

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

Пример Java-кода для взаимного исключения с помощью монитора

Как достигается координация/синхронизация с помощью монитора?

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

Когда поток вызывает метод wait() в отношении объекта, поток приостанавливается и добавляется к набору ожидания для ожидания, пока какой-либо другой поток не вызовет notify() или notifyAll() для того же объекта.

Метод notify() используется для пробуждения потоков, находящихся в наборе ожидания монитора определенного объекта.Существует два способа уведомления ожидающих потоков.

  • notify() --> Для всех потоков, ожидающих набора ожидания, метод notify() уведомляет любого из них о произвольном пробуждении. Выбор того, какой именно поток активировать, недетерминирован и зависит от JVM.
  • notifyAll() --> Этот метод просто пробуждает все потоки, ожидающие набора ожидания. Пробужденные потоки не смогут продолжать работу, пока текущий поток не снимет блокировку с этого объекта. Пробужденные потоки будут конкурировать обычным образом с любыми другими потоками, которые могут активно конкурировать за синхронизацию.

Пример кода Java для достижения синхронизации с использованием монитора в проблеме производителя-потребителя

Если вы изучали операционные системы в колледже, вы, возможно, помните, что монитор — это важная концепция синхронизации в операционных системах. Он также используется в синхронизации Java. В этом посте используется аналогия для объяснения основной идеи «монитора».

<р>1. Что такое монитор?

Монитор можно рассматривать как здание, в котором есть специальная комната. Специальная комната может быть занята только одним клиентом (потоком) одновременно. Комната обычно содержит некоторые данные и код.

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

Вкратце, монитор — это средство, которое отслеживает доступ потоков к специальной комнате. Это гарантирует, что только один поток может получить доступ к защищенным данным или коду.

<р>2. Как это реализовано в Java?

В виртуальной машине Java каждый объект и класс логически связаны с монитором. Для реализации возможности взаимного исключения мониторов с каждым объектом и классом связана блокировка (иногда называемая мьютексом). В книгах по операционным системам это называется семафором, а мьютекс — бинарным семафором.

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

Чтобы претендовать на область монитора, которая означает, что данные недоступны более чем для одного потока, Java предоставляет синхронизированные операторы и синхронизированные методы. После того, как в код встроено ключевое слово synchronized, это область монитора. Блокировки реализуются в фоновом режиме автоматически с помощью JVM.

<р>3. Какая часть кода синхронизации Java является монитором?

Мы знаем, что каждый объект/класс связан с монитором. Я думаю, хорошо сказать, что у каждого объекта есть монитор, поскольку каждый объект может иметь свою собственную критическую секцию и способен отслеживать последовательность потоков.

Чтобы обеспечить совместную работу различных потоков, в Java предусмотрены функции wait() и notify() для приостановки потока и пробуждения другого потока, ожидающего объекта соответственно. Кроме того, есть еще 3 версии:

wait(long timeout, int nanos) wait(long timeout), уведомленный другими потоками или уведомленный по тайм-ауту. уведомить (всех)

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

Читайте также: