Spec-Zone .ru
спецификации, руководства, описания, API
Содержание | Предыдущий | Следующий | Индекс

ГЛАВА 17

Потоки и Блокировки


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

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

synchronized оператор (§14.17) выполняет два специальных действия, относящиеся только к многопоточной работе: (1) после вычислений ссылки на объект, но прежде, чем выполнить его тело, это блокирует блокировку, связанную с объектом, и (2) после того, как выполнение тела завершилось, или обычно или резко, это разблокировало ту же самую блокировку. Как удобство, может быть объявлен метод synchronized; такой метод ведет себя, как будто его тело содержалось в a synchronized оператор.

Методы wait (§20.1.6, §20.1.7, §20.1.8), notify (§20.1.9), и notifyAll (§20.1.10) класса Object поддерживайте эффективную передачу управления от одного потока до другого. Вместо того, чтобы просто "вращаться" (неоднократно блокировка и разблокирование объекта видеть, изменилось ли некоторое внутреннее состояние), который использует вычислительное усилие, поток может приостановить себя использование wait до тех пор, пока другой поток пробуждает это использование notify. Это является особенно соответствующим в ситуациях, где у потоков есть отношение производителя-потребителя (активно сотрудничающий на общей цели), а не отношение взаимного исключения (пытающийся избежать конфликтов, совместно используя общий ресурс).

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

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

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

Кратко помещенный, они - важные последствия правил:

17.1 Терминология и Платформа

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

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

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

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

Использование или присваивается, действие является сильно связанным взаимодействием между механизмом выполнения потока и рабочей памятью потока. Блокировка или разблокировала действие, сильно связанное взаимодействие между механизмом выполнения потока и основной памятью. Но передача данных между основной памятью и рабочей памятью потока слабо связывается. Когда данные копируются от основной памяти до рабочей памяти, два действия должны произойти: действие чтения, выполняемое основной памятью, сопровождаемой некоторое время спустя соответствующим действием загрузки, выполняется рабочей памятью. Когда данные копируются от рабочей памяти до основной памяти, два действия должны произойти: действие хранилища, выполняемое рабочей памятью, сопровождаемой некоторое время спустя соответствующим действием записи, выполняется основной памятью. Может быть некоторое время транспортировки между основной памятью и рабочей памятью, и время транспортировки может отличаться для каждой транзакции; таким образом действия, инициируемые потоком на различных переменных, могут просматриваемый другим потоком как происходящий в различном порядке. Для каждой переменной, однако, действия в основной памяти от имени любого потока выполняются в том же самом порядке как соответствующие действия тем потоком. (Это объясняется более подробно ниже.)

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

Вот подробные определения каждого из действий:

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

17.2 Порядок выполнения

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

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

Потоки не взаимодействуют непосредственно; они связываются только через совместно используемую основную память. Отношения между действиями потока и действиями основной памяти ограничиваются тремя способами:

Большинство правил в следующих разделах далее ограничивает порядок, в котором имеют место определенные действия. Правило может утвердить, что одно действие должно предшествовать или следовать за некоторым другим действием. Отметьте, что это отношение является переходным: если действие, Необходимость предшествует действию B, и B, должно предшествовать C, то Необходимость предшествует C. Программист должен помнить, что эти правила являются единственными ограничениями на упорядочивание действий; если никакое правило или комбинация правил не подразумевают, что действие, Необходимость предшествует действию B, то реализация Java свободна выполнить действие B перед действием A, или выполнить действие B одновременно с действием A. Эта свобода может быть ключом к хорошей производительности. Наоборот, реализация не обязана использовать в своих интересах все свободы, данные это.

В правилах, которые следуют, формулировка "B должна вмешаться между A, и C" означает, что действие B должно следовать за действием A и предшествовать действию C.

17.3 Правила о Переменных

Позвольте T быть потоком и V быть переменной. Есть определенные ограничения на действия, выполняемые T относительно V:

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

Есть также определенные ограничения на чтение и действия записи, выполняемые основной памятью:

Отметьте, что это последнее правило применяется только к действиям потоком на той же самой переменной. Однако, есть более строгое правило для volatile переменные (§17.7).

17.4 Неатомарная Обработка double и long

Если a double или long переменная не объявляется volatile, тогда в целях загрузки, хранилища, чтения, и действий записи они обрабатываются, как будто они были двумя переменными 32 битов каждый: везде, где правила требуют одного из этих действий, два таких действия выполняются, один для каждой 32-разрядной половины. Способ тот, в который 64 бита a double или long переменная кодируется в два 32-разрядных количества, является зависящим от реализации.

Это имеет значение только потому, что чтение или запись a double или long переменная может быть обработана фактической основной памятью как два 32-разрядных чтения или действия записи, которые могут быть разделены вовремя с другими действиями, прибывающими между ними. Следовательно, если два потока одновременно присваивают отличные значения тому же самому, совместно использованному не -volatile double или long переменная, последующее использование той переменной может получить значение, которое не равно любому из присвоенных значений, но небольшому количеству зависящей от реализации смеси двух значений.

Реализация свободна реализовать загрузку, хранилище, чтение, и действия записи для double и long значения как атомарные 64-разрядные действия; фактически, это строго поощряется. Модель делит их на 32-разрядные половины ради нескольких в настоящий момент популярных микропроцессоров, которые не в состоянии обеспечить эффективные атомарные транзакции памяти на 64-разрядных количествах. Было бы более просто для Java определить все транзакции памяти на единственных переменных как атомарные; это более сложное определение является прагматической концессией текущей аппаратной практике. В будущем может быть устранена эта концессия. Тем временем программистов предостерегают всегда явно синхронизировать доступ к совместно используемому double и long переменные.

17.5 Правила о Блокировках

Позвольте T быть потоком и L быть блокировкой. Есть определенные ограничения на действия, выполняемые T относительно L:

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

17.6 Правила о Взаимодействии Блокировок и Переменных

Позвольте T быть любым потоком, позволять V быть любой переменной, и позволять L быть любой блокировкой. Есть определенные ограничения на действия, выполняемые T относительно V и L:

17.7 Правила для Энергозависимых Переменных

Если переменная объявляется volatile, тогда дополнительные ограничения применяются к действиям каждого потока. Позвольте T быть потоком и позволять V и W быть энергозависимыми переменными.

17.8 Наделенные даром предвидения Действия Хранилища

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

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

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

17.9 Обсуждение

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

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

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

17.10 Примеров: Возможная Подкачка

Рассмотрите класс, у которого есть переменные класса a и b и методы hither и yon:


class Sample {
	int a = 1, b = 2;
	void hither() {
		a = b;
	}
	void yon() {
		b = a;
	}
}
Теперь предположите, что два потока создаются, и что один поток вызывает hither в то время как другой поток вызывает yon. Каков необходимый набор действий и каковы ограничения упорядочивания?

Давайте рассматривать поток, который вызывает hither. Согласно правилам, этот поток должен выполнить использование b сопровождаемый присваиванием a. Это - пустой минимум, требуемый выполнить звонок в метод hither.

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

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

Ситуация для потока, который вызывает yon подобно, но с ролями a и b обмененный.

Полный набор действий может быть изображен следующим образом:

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

В каком порядок может действия основной памятью происходить? Единственное ограничение состоит в том, что это не возможно оба для записи a предшествовать чтению a и для записи b предшествовать чтению b, потому что стрелки причинной связи в схеме сформировали бы цикл так, чтобы действие должно было предшествовать себе, который не позволяется. Предполагая, что дополнительное хранилище и действия записи должны произойти, есть три возможных упорядочивания, в которых основная память могла бы законно выполнить свои действия. Позволить ha и hb будьте рабочими копиями a и b для hither поток, позволить ya и yb будьте рабочими копиями для yon поток, и позволял ma и mb будьте основными копиями в основной памяти. Первоначально ma=1 и mb=2. Затем три возможных упорядочивания действий и получающихся состояний следующие:

Таким образом конечный результат мог бы состоять в том что в основной памяти, b копируется в a, a копируется в b, или значения a и b подкачиваются; кроме того рабочие копии переменных могли бы или не могли бы согласиться. Было бы неправильно, конечно, предположить, что любой из этих результатов более вероятен чем другой. Это - одно место, в котором поведение программы Java обязательно зависимо от синхронизации.

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

Теперь предположите, что мы изменяем пример, чтобы использовать synchronized методы:


class SynchSample {
	int a = 1, b = 2;
	synchronized void hither() {
		a = b;
	}
	synchronized void yon() {
		b = a;
	}
}
Снова давайте рассматривать поток, который вызывает hither. Согласно правилам, этот поток должен выполнить действие блокировки (на объекте класса для класса SynchSample) перед телом метода hither выполняется. Это сопровождается использованием b и затем присваивание a. Наконец, разблокировать действие на объекте класса должно быть выполнено после тела метода hither завершается. Это - пустой минимум, требуемый выполнить звонок в метод hither.

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

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

Ситуация для потока, который вызывает yon подобно, но с ролями a и b обмененный.

Полный набор действий может быть изображен следующим образом:

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

В то время как получающееся состояние зависимо от синхронизации, можно заметить, что два потока обязательно договорятся о значениях a и b.

17.11 Примеров: не в порядке Записи

Этот пример подобен этому в предыдущем разделе, за исключением того, что один метод присваивается к обеим переменным, и другой метод читает обе переменные. Рассмотрите класс, у которого есть переменные класса a и b и методы to и fro:


class Simple {
	int a = 1, b = 2;
	void to() {
		a = 3;
		b = 4;
	}
	void fro() {
		System.out.println("a= " + a + ", b=" + b);
	}
}
Теперь предположите, что два потока создаются, и что один поток вызывает to в то время как другой поток вызывает fro. Каков необходимый набор действий и каковы ограничения упорядочивания?

Давайте рассматривать поток, который вызывает to. Согласно правилам, этот поток должен выполнить присваивание a сопровождаемый присваиванием b. Это - пустой минимум, требуемый выполнить звонок в метод to. Поскольку нет никакой синхронизации, это в опции реализации, сохранить ли присвоенные значения назад к основной памяти! Поэтому поток, который вызывает fro может получить также 1 или 3 для значения a, и независимо может получить также 2 или 4 для значения b.

Теперь предположите это to synchronized но fro не:


class SynchSimple {
	int a = 1, b = 2;
	synchronized void to() {
		a = 3;
		b = 4;
	}
	void fro() {
		System.out.println("a= " + a + ", b=" + b);
	}
}
В этом случае метод to будет вынужден сохранить присвоенные значения назад к основной памяти перед разблокировать действием в конце метода. Метод fro должен, конечно, использовать a и b (в том порядке), и так должен загрузить значения для a и b от основной памяти.

Полный набор действий может быть изображен следующим образом:

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

В каком порядок может действия основной памятью происходить? Отметьте, что правила не требуют той записи a происходите перед записью b; и при этом они не требуют того чтения a происходите прежде, чем считано b. Кроме того, даже при том, что метод to synchronized, метод fro не synchronized, таким образом, нет ничего, чтобы препятствовать тому, чтобы действия чтения произошли между блокировкой и разблокировали действия. (Дело в том, что объявление одного метода synchronized не делает себя, заставляют тот метод вести себя, как будто это было атомарным.)

В результате метод fro мог все еще получить также 1 или 3 для значения a, и независимо мог получить также 2 или 4 для значения b. В частности fro мог бы наблюдать значение 1 для a и 4 для b. Таким образом, даже при том, что to делает присваивание a и затем присваивание b, действия записи к основной памяти, как может наблюдать другой поток, происходят как будто в противоположном порядке.

Наконец, предположите это to и fro оба synchronized:


class SynchSynchSimple {
	int a = 1, b = 2;
	synchronized void to() {
		a = 3;
		b = 4;
	}
	synchronized void fro() {
		System.out.println("a= " + a + ", b=" + b);
	}
}
В этом случае, действия метода fro не может быть чередован с действиями метода to, и так fro напечатает любого"a=1, b=2"или"a=3, b=4".

17.12 Потоков

Потоки создаются и управляются встроенными классами Thread (§20.20) и ThreadGroup (§20.21). Создание a Thread объект создает поток, и это - единственный способ создать поток. Когда поток создается, это еще не активно; это начинает работать когда start метод (§20.20.14) вызывают.

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

17.13 Блокировок и Синхронизация

Есть блокировка, связанная с каждым объектом. Язык Java не обеспечивает способ выполнить отдельную блокировку и разблокировать действия; вместо этого, они неявно выполняются высокоуровневыми конструкциями, которые располагают всегда соединить такие действия правильно. (Мы отмечаем, однако, что виртуальная машина Java обеспечивает отдельный monitorenter и monitorexit инструкции, которые реализуют блокировку и разблокировали действия.)

synchronized оператор (§14.17) вычисляет ссылку на объект; это тогда пытается выполнить действие блокировки на том объекте и не продолжается далее, пока действие блокировки успешно не завершилось. (Действие блокировки может быть задержано, потому что правила о блокировках могут препятствовать тому, чтобы основная память участвовала, пока некоторый другой поток не готов выполнить один, или больше разблокировало действия.) После того, как действие блокировки было выполнено, тело synchronized оператор выполняется. Если выполнение тела когда-либо завершается, или обычно или резко, разблокировать действие автоматически выполняется на той же самой блокировке.

A synchronized метод (§8.4.3.5) автоматически выполняет действие блокировки, когда он вызывается; его тело не выполняется, пока действие блокировки успешно не завершилось. Если метод является методом экземпляра, он блокирует блокировку, связанную с экземпляром, для которого он был вызван (то есть, объект, который будет известен как this во время выполнения тела метода). Если метод static, это блокирует блокировку, связанную с Class объект, который представляет класс, в котором определяется метод. Если выполнение тела метода когда-либо завершается, или обычно или резко, разблокировать действие автоматически выполняется на той же самой блокировке.

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

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

17.14 Ожидают Наборы и Уведомление

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

Ожидайте наборы используются методами wait (§20.1.6, §20.1.7, §20.1.8), notify (§20.1.9), и notifyAll (§20.1.10) класса Object. Эти методы также взаимодействуют с механизмом планирования для потоков (§20.20).

Метод wait должен быть вызван для объекта только, когда текущий поток (вызывают это T) уже заблокировал блокировку объекта. Предположите, что поток T фактически выполнил действия блокировки N, которые не были соответствующими, разблокировали действия. wait метод тогда добавляет текущий поток к ожидать набору для объекта, отключает текущий поток в целях планирования потоков, и выполняет N, разблокировали действия, чтобы оставить блокировку. Поток T тогда бездействует, пока одна из трех вещей не происходит:

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

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

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


Содержание | Предыдущий | Следующий | Индекс

Спецификация языка Java (HTML, сгенерированный Блинчиком "сюзет" Pelouch 24 февраля 1998)
Авторское право © Sun Microsystems, Inc 1996 года. Все права защищены
Пожалуйста, отправьте любые комментарии или исправления к doug.kramer@sun.com

free hit counter