Что является монитором у статического синхронизированного класса
Собеседование по Java — многопоточность (вопросы и ответы)
Вопросы и ответы для собеседования Java по теме — многопоточность.
К списку вопросов по всем темам
Вопросы
1. Дайте определение понятию “процесс”.
2. Дайте определение понятию “поток”.
3. Дайте определение понятию “синхронизация потоков”.
4. Как взаимодействуют программы, процессы и потоки?
5. В каких случаях целесообразно создавать несколько потоков?
6. Что может произойти если два потока будут выполнять один и тот же код в программе?
7. Что вы знаете о главном потоке программы?
8. Какие есть способы создания и запуска потоков?
9. Какой метод запускает поток на выполнение?
10. Какой метод описывает действие потока во время выполнения?
11. Когда поток завершает свое выполнение?
12. Как синхронизировать метод?
13. Как принудительно остановить поток?
14. Дайте определение понятию “поток-демон”.
15. Как создать поток-демон?
16. Как получить текущий поток?
17. Дайте определение понятию “монитор”.
18. Как приостановить выполнение потока?
19. В каких состояниях может пребывать поток?
20. Что является монитором при вызове нестатического и статического метода?
21. Что является монитором при выполнении участка кода метода?
22. Какие методы позволяют синхронизировать выполнение потоков?
23. Какой метод переводит поток в режим ожидания?
24. Какова функциональность методов notify и notifyAll?
25. Что позволяет сделать метод join?
26. Каковы условия вызова метода wait/notify?
27. Дайте определение понятию “взаимная блокировка”.
28. Чем отличаются методы interrupt, interrupted, isInterrupted?
29. В каком случае будет выброшено исключение InterruptedException, какие методы могут его выбросить?
30. Модификаторы volatile и метод yield().
31. Пакет java.util.concurrent
32. Есть некоторый метод, который исполняет операцию i++. Переменная i типа int. Предполагается, что код будет исполнятся в многопоточной среде. Следует ли синхронизировать блок?
33. Что используется в качестве mutex, если метод объявлен static synchronized? Можно ли создавать новые экземпляры класса, пока выполняется static synchronized метод?
34. Предположим в методе run возник RuntimeException, который не был пойман. Что случится с потоком? Есть ли способ узнать о том, что Exception произошел (не заключая все тело run в блок try-catch)? Есть ли способ восстановить работу потока после того как это произошло?
35. Какие стандартные инструменты Java вы бы использовали для реализации пула потоков?
36.Что такое ThreadGroup и зачем он нужен?
37.Что такое ThreadPool и зачем он нужен?
38.Что такое ThreadPoolExecutor и зачем он нужен?
39.Что такое «атомарные типы» в Java?
40.Зачем нужен класс ThreadLocal?
41.Что такое Executor?
42.Что такое ExecutorService?
43.Зачем нужен ScheduledExecutorService?
Ответы
1. Дайте определение понятию “процесс”.
Процесс — это совокупность кода и данных, разделяющих общее виртуальное адресное пространство. Процессы изолированы друг от друга, поэтому прямой доступ к памяти чужого процесса невозможен (взаимодействие между процессами осуществляется с помощью специальных средств). Для каждого процесса ОС создает так называемое «виртуальное адресное пространство», к которому процесс имеет прямой доступ. Это пространство принадлежит процессу, содержит только его данные и находится в полном его распоряжении. Операционная система же отвечает за то, как виртуальное пространство процесса проецируется на физическую память.
Многопоточность в Java: http://habrahabr.ru/post/164487/
2. Дайте определение понятию “поток”.
Один поток («нить» или «трэд») – это одна единица исполнения кода. Каждый поток последовательно выполняет инструкции процесса, которому он принадлежит, параллельно с другими потоками этого процесса.
Thinking in Java.Параллельное выполнение. http://wikijava.it-cache.net/index.php@title=Glava_17_Thinking_in_Java_4th_edition.html
3. Дайте определение понятию “синхронизация потоков”.
Синхронизация относится к многопоточности. Синхронизированный блок кода может быть выполнен только одним потоком одновременно.
Java поддерживает несколько потоков для выполнения. Это может привести к тому, что два или более потока получат доступ к одному и тому же полю или объекту. Синхронизация — это процесс, который позволяет выполнять все параллельные потоки в программе синхронно. Синхронизация позволяет избежать ошибок согласованности памяти, вызванных непоследовательным доступом к общей памяти.
Когда метод объявлен как синхронизированный — нить держит монитор для объекта, метод которого исполняется. Если другой поток выполняет синхронизированный метод, ваш поток заблокируется до тех пор, пока другой поток не отпустит монитор.
Синхронизация достигается в Java использованием зарезервированного слова synchronized. Вы можете использовать его в своих классах определяя синхронизированные методы или блоки. Вы не сможете использовать synchronized в переменных или атрибутах в определении класса.
Синхронизация потоков, блокировка объекта и блокировка класса info.javarush.ru: http://goo.gl/gW4ONp
4. Как взаимодействуют программы, процессы и потоки?
Чаще всего одна программа состоит из одного процесса, но бывают и исключения (например, браузер Chrome создает отдельный процесс для каждой вкладки, что дает ему некоторые преимущества, вроде независимости вкладок друг от друга). В каждом процессе может быть создано множество потоков. Процессы разделены между собой (>программы), потоки в одном процессе могут взаимодействовать друг с другом (методы wait, notify, join и т.д.).
5. В каких случаях целесообразно создавать несколько потоков?
Многопоточные приложения применяются в случаях, когда можно разделить программу на несколько относительно независимых частей. В этом случае чтобы один код не ждал другой их помещают в различные потоки. В качестве примера можно привести программу с графическим интерфейсом — пока выполняются какие-либо длительные вычисления в одном потоке, интерфейс может быть доступен пользователю и не зависать, если он выполняется в другом потоке.
6. Что может произойти если два потока будут выполнять один и тот же код в программе?
Если используются не синхронизированные данные, то может произойти ситуация, когда код работает уже с устаревшими данными. Например, в первом потоке идет изменение каких-либо полей, а в это время второй поток читает эти поля.
7. Что вы знаете о главном потоке программы?
Маленькие программы на Java обычно состоят из одной нити, называемой «главной нитью» (main thread). Но программы побольше часто запускают дополнительные нити, их еще называют «дочерними нитями». Главная нить выполняет метод main и завершается. Аналогом такого метода main, для дочерних нитей служит метод run интерфейса Runnable. Много потоков — много методов main (run()).
8. Какие есть способы создания и запуска потоков?
Существует несколько способов создания и запуска потоков.
С помощью класса, реализующего Runnable
С помощью класса, расширяющего Thread
С помощью класса, реализующего java.util.concurrent.Callable
А как же всё-таки работает многопоточность? Часть I: синхронизация
(пост из серии «я склонировал себе исходники hotspot, давайте посмотрим на них вместе»)
Все, кто сталкивается с многопоточными проблемами (будь то производительность или непонятные гейзенбаги), неизбежно сталкиваются в процессе их решения с терминами вроде «inflation», «contention», «membar», «biased locking», «thread parking» и тому подобным. А вот все ли действительно знают, что за этими терминами скрывается? К сожалению, как показывает практика, не все.
В надежде исправить ситуацию, я решил написать цикл статей на эту тему. Каждая из них будет построена по принципу «сначала кратко опишем, что должно происходить в теории, а потом отправимся в исходники и посмотрим, как это происходит там». Таким образом, первая часть во многом применима не только к Java, а потому и разработчики под другие платформы могут найти для себя что-то полезное.
Перед прочтением глубокого описания полезно убедиться в том, что вы в достаточной мере разбираетесь в Java Memory Model. Изучить её можно, например, по слайдам Сергея Walrus Куксенко или по моему раннему топику. Также отличным материалом является вот эта презентация, начиная со слайда #38.
Теоретический минимум
Прежде, чем продолжить, определим важное понятие:
contention — ситуация, когда несколько сущностей одновременно пытаются владеть одним и тем же ресурсом, который предназначен для монопольного использования
От того, есть ли contention на владение монитором, очень сильно зависит то, как производится его захват. Монитор может находиться в следующих состояниях:
На этом абстрактные рассуждения заканчиваются, и мы погружаемся в то, как оно реализовано в hotspot.
Заголовки объектов
Содержимое mark words
Вы заметили, что в случае biased не хватило места одновременно и для identity hash code и для threadID + epoch? А это так, и отсюда есть интересное следствие: в hotspot вызов System.identityHashCode приведёт к revoke bias объекта.
Далее, когда монитор занят, в mark word хранится указатель на то место, где хранится настоящий mark word. В стеке каждого потока есть несколько «секций», в которых хранятся разные вещи. Нас интересует та, где хранятся lock record’ы. Туда мы и копируем mark word объекта при легковесной блокировке. Потому, кстати, thin-locked объекты называют stack locked. Раздутый монитор может храниться как у потока, который его раздул, так и в глобальном пуле толстых мониторов.
Пора перейти к коду.
Простенький пример использования synchronized
Начнём с такого класса:
и посмотрим, во что он скомпилируется:
В общем случае код VM’ного хелпера для какого-нибудь действия может по содержанию отличаться от вклеенного JIT’ом. Вплоть до того, что некоторые оптимизации со стороны JIT’а могут просто не портированы в интерпретатор
Также Лёша порекомендовал взять в зубы PrintAssembly и смотреть сразу на скомпилированный и за-JIT-нутый код, но я решил начать с пути меньшего сопротивления, а потом уже посмотреть, как же оно на самом деле тм
monitorenter
Если CAS не удался, то мы проверяем, не являемся ли мы уже владельцами монитора (рекурсивный захват); и если да, то успех снова за нами, единственное, что мы делаем — это записываем в displaced header у себя на стеке NULL (дальше узнаем, зачем это нужно). В противном случае мы делаем следующий вызов:
fast_enter
safepoint — состояние виртуальной машины, в котором исполнение потоков остановлено в безопасных местах. Это позволяет проводить интрузивные операции, вроде revoke bias у монитора, которым поток в данный момент владеет, деоптимизации или взятия thread dump.
Как вы можете догадаться, bulk-операции — хитрые оптимизации, которые упрощают передачу большого числа объектов между потоками. Если бы не было этой оптимизации, то было бы опасно включать UseBiasedLocking по умолчанию, поскольку тогда большой класс приложений вечно бы занимался revocation’ами и rebiasing’ами.
Если быстрым путём захватить поток не удалось (т.е, был сделан revoke bias), мы переходим к захвату thin-лока.
slow_enter
После раздувания монитора необходимо в него зайти. Метод ObjectMonitor::enter делает именно это, применяет все мыслимые и немыслимые хитрости, чтобы избежать парковки потока. В число этих хитростей входят, как вы уже могли догадаться, попытки захватить с помощью spin loop’а, с помощью однократных CAS-ов и прочих «халявных методов». Кстати, кажется, я нашёл небольшое несоответствие комментариев с происходящим. вот мы один раз пытаемся войти в монитор spin loop’ом, утверждая, что это делаем лишь однажды:
А вот чуть дальше, в вызываемом методе enterI делаем это снова, опять говоря про лишь один раз:
Мда, парковка на уровне операционной системы — это настолько страшно, что мы готовы почти на всё, чтобы её избежать. Давайте разберёмся, что же в ней такого ужасного.
Парковка потоков задним про ходом
Должен заметить, что мы сейчас подошли к коду, который писали очень давно, и это заметно. Есть много дубликации, переинжениринга и прочих приятностей. Впрочем, наличие комментариев типа «убрать этот костыль» и «объединить эти с тем» слегка успокаивают.
Итак, что же такое парковка потоков? Все наверняка слышали, что у каждого монитора есть так называемый Entry List (не путать с Waitset) Так вот: он действительно есть, хотя он и является на самом деле очередью. После всех провалившихся попыток дёшево войти в монитор, мы добавляем себя именно в эту очередь, после чего паркуемся:
Прежде чем перейти непосредственно к парковке, обратим внимание на то, что тут она может быть timed или не timed, в зависимости от того, является ли текущий поток ответственным. Ответственных потоков всегда не более одного, и они нужны для того, чтобы избежать так называемого stranding’a: печальки, когда монитор освободился, но все потоки в wait set по-прежнему запаркованы и ждут чуда. Когда есть ответственный, он автоматически просыпается время от времени (чем больше раз произошёл futile wakeup — пробуждение, после которого захватить лок не удалось — тем больше время парковки. Обратите внимание, что оно не превышает 1000 мсек) и пытается войти в монитор. Остальные потоки могут ждать пробуждения хоть целую вечность.
Thread scheduling на уровне ОС
Что ж, самое время забраться в ядро linux и посмотреть, как там работает шедулер. Исходники linux, как известно, лежат в git, и склонировать шедулер можно так:
Конечно, не стоит думать, что поток будет исполняться ровно столько времени, сколько ему отведено. Он может сам решить, что сделал всё, что хотел (например, заблокироваться на каком-нибудь I/O-вызове), либо его может насильно выдернуть раньше времени шедулер, отдав остаток его кванта кому-нибудь другому. А может и наоборот решить продлить квант по какой-нибудь причине. Кроме того, у потоков есть приоритет, который, в общем-то, понятно, что делает.
Теперь вы, должно быть, поняли, что парковка — дорого. Мало того, что это системный вызов, так ещё и оказывается, что шедулер может распарковать поток заметно позже, чем вам бы хотелось, поскольку в системе может быть ещё куча потоков, которые шедулер решит исполнять вместо вашего.
Но и это, кстати, ещё не всё: когда процессору на исполнение отдаётся другой поток, происходит смена контекста — тоже довольно дорогая операция, которая может занимать до десятка микросекунд. Более того: каким бы невероятным это не могло казаться, разные потоки как правило интересуют разные данные, потому в кеше может оказаться что-то, что этому потоку совершенно не нужно.
Если смена контекста происходит часто, а потоки работают небольшой промежуток времени, может оказаться, что процессор загружен техническими операциями. Такое может быть, если высок contention, но все воюющие хотят владеть монитором лишь непродолжительный промежуток времени.
monitorexit
В случае с biased locking мы, в общем-то, ничего и не делаем. Мы обнаруживаем, что в displaced header хранится NULL, и просто выходим. Отсюда интересный момент: при попытке отпустить не занятый в данный момент biased lock интерпретатор не выкенет IllegalMonitorStateException (но за такими вещами следит верификатор байт-кода).
В третьем случае мы отпускаем лок и выставляем мембары, после чего смотрим, нет ли сейчас какого-нибудь распаркованного потока, который готов прямо сейчас забрать лок. Такое возможно, если он проснулся и пытается захватить монитор с помощью, например, TrySpin (см. выше). Если такой обнаруживается, то наша работа на этом завершена. Также она завершена, если очередь потоков, которые хотят получить лок, пуста.
Собственно, если очень сильно не вдаваться в детали, то это всё, что можно сказать об освобождении монитора.
Если бы мы написали наш изначальный java-код вот так вот:
то байт-код у такого метода заметно короче:
Также, после окончания выполнения тела метода идёт выход из монитора, если метод synchronized.
Wait и notify
Первый добавляет себя в wait set (на самом деле очередь) и паркуется до тех пор, пока ему не пора будет просыпаться (прошло время, которое просили подождать; произошло прерывание или кто-то вызвал notify).
Notify же вытаскивает из wait set один поток и добавляет его, в зависимости от политик, в какое-то место в очереди тех, кто хочет захватить монитор. NotifyAll отличается лишь тем, что вытаскивает из wait set всех.
Memory effects
Прямо перед выходом из wait выставляется явный fence, чтобы прогарантировать HB.
Замечание от Майора О. aka Disclaimer
Stay tuned
В следующих сериях, в первую очередь, необходимо рассказать о memory barriers, которые крайне важны для обеспечения happens-before в JMM. Их очень удобно рассматривать на примере volatile полей, что я в дальнейшем и сделаю. Также стоит обратить внимание на final-поля и безопасную публикацию, но их уже осветили TheShade и cheremin в своих статьях, потому их и можно почитать интересующимся почитать (только осторожно). И, наконец, можно ждать наполненный PrintAssembly рассказ о том, как оно всё отличается, когда в дело вступает JIT.
And one more thing ©
Желающим повторить путешествие: я пользовался ревизией 144f8a1a43cb из jdk7u. Если ваша ревизия отличается, то могут отличаться и номера строк — К.О.
Biased locking включается не сразу после запуска виртуальной машины, а спустя BiasedLockingStartupDelay миллисекунд (4000 по умолчанию). Это сделано, поскольку иначе в процессе запуска и инициализации виртуальной машины, загрузки классов и всего прочего появилось бы огромное число safepoints, вызванных постоянным revoke bias у живых объектов.
Большое спасибо доблестным TheShade, artyushov, cheremin и AlexeyTokar за то, что они (вы|про)читали статью перед публикацией, убедившись тем самым, что я не принесу в массы вместо света какую-то бредовню, наполненную тупыми шутками и очепатками.
Monitor Класс
Определение
Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
Предоставляет механизм для синхронизации доступа к объектам.
Примеры
В следующем примере класс используется Monitor для синхронизации доступа к одному экземпляру генератора случайных чисел, представленного Random классом. В примере создается десять задач, каждый из которых асинхронно выполняется в потоке пула потоков. Каждая задача создает 10 000 случайных чисел, вычисляет их среднее значение и обновляет две переменные уровня процедуры, которые сохраняют общую сумму количества созданных случайных чисел и их сумму. После выполнения всех задач эти два значения затем используются для вычисления общего значения.
Так как доступ к ним можно получить из любой задачи, выполняемой в потоке пула потоков, доступ к переменным total и n также должен быть синхронизирован. Interlocked.AddДля этой цели используется метод.
Как показывает результат этого примера, синхронизированный доступ обеспечивает, что вызывающий поток выходит из защищенного ресурса до того, как другой поток получит доступ к этому ресурсу; каждый поток ожидает своего предшественника. С другой стороны, без блокировки метод UnSyncResource.Access вызывается в том порядке, в котором потоки получают к нему доступ.
Комментарии
Содержание этой статьи
Обзор класса Monitor.
Monitor имеет следующие возможности.
Он связан с объектом по требованию.
Он не связан. Это означает, что его можно вызывать непосредственно из любого контекста.
Экземпляр Monitor класса не может быть создан; методы Monitor класса являются статическими. Каждому методу передается синхронизированный объект, который управляет доступом к критической секции.
Используйте Monitor класс для блокировки объектов, отличных от строк (то есть ссылочных типов, отличных от String ), а не типов значений. Дополнительные сведения см. в разделе перегрузки Enter метода и объекта блокировки далее в этой статье.
В следующей таблице описаны действия, которые могут выполняться потоками, обращающимися к синхронизированным объектам.
Действие | Описание |
---|---|
Enter, TryEnter | Получает блокировку для объекта. Это действие также помечает начало критического раздела. Ни один другой поток не может войти в критическую секцию, если он не будет выполнять инструкции в критическом разделе, используя другой заблокированный объект. |
Wait | Освобождает блокировку объекта, чтобы позволить другим потокам блокировать и получать доступ к объекту. Вызывающий поток ожидает, пока другой поток пообращается к объекту. Импульсные сигналы используются для уведомления ожидающих потоков об изменениях в состоянии объекта. |
Pulse (сигнал), PulseAll | Отправляет сигнал одному или нескольким ожидающим потокам. Сигнал уведомляет ожидающий поток о том, что состояние заблокированного объекта изменилось, а владелец блокировки готов освободить блокировку. Ожидающий поток помещается в очередь готовности объекта, чтобы она могла в конечном итоге получить блокировку для объекта. После блокировки потока он может проверить новое состояние объекта, чтобы узнать, было ли достигнуто требуемое состояние. |
Exit | Освобождает блокировку объекта. Это действие также помечает конец критической секции, защищенной заблокированным объектом. |
Объект Lock
класс Monitor состоит из static методов (в C#) или Shared (в Visual Basic), которые работают с объектом, который управляет доступом к критическому разделу. Для каждого синхронизированного объекта сохраняются следующие сведения.
Ссылка на поток, который в настоящий момент удерживает блокировку.
Ссылка на очередь готовности, которая содержит потоки, готовые к получению блокировки.
Ссылка на очередь ожидания, которая содержит потоки, ожидающие уведомления об изменении состояния заблокированного объекта.
Monitor блокирует объекты (то есть ссылочные типы), а не типы значений. Хотя можно передать тип значения в Enter и Exit, он упаковывается отдельно для каждого вызова. Поскольку при каждом вызове создается отдельный объект, Enter никогда не выполняет блокировку, а код, который он предположительно защищает, на самом деле не синхронизируется. Кроме того, объект, переданный в Exit, отличается от объекта, переданного в Enter, поэтому Monitor вызывает исключение SynchronizationLockException с сообщением «Для не синхронизированного блока кода вызван метод синхронизации объектов».
Хотя можно упаковать переменную типа значения перед вызовом Enter и Exit, как показано в следующем примере, и передать тот же упакованный объект в оба метода, такой подход не дает никаких преимуществ. Изменения неупакованной переменной не отражаются в упакованной копии, и возможность изменения значения упакованной копии отсутствует.
При выборе объекта для синхронизации следует блокировать только закрытые или внутренние объекты. Блокировка внешних объектов может привести к взаимоблокировкам, так как несвязанный код может выбирать те же объекты для блокировки в различных целях.
Критическая секция
Используйте Enter методы и Exit для обозначения начала и конца критической секции.
Если критическая секция является набором смежных инструкций, то блокировка, полученная Enter методом, гарантирует, что только один поток может выполнить вложенный код с заблокированным объектом. В этом случае рекомендуется поместить этот код в try блок и поместить вызов Exit метода в finally блок. Это гарантирует снятие блокировки даже при возникновении исключения. Этот шаблон показан в следующем фрагменте кода.
Это средство обычно используется для синхронизации доступа к статическому методу или экземпляру класса.
Обратите внимание, что атрибут заставляет текущий поток удерживать блокировку до тех пор, пока метод не вернет значение. если блокировку можно освободить раньше, используйте Monitor класс, оператор блокировки C# или оператор Visual Basic SyncLock внутри метода, а не атрибута.
Хотя для Enter Exit операторов и, которые блокируют и освобождают данный объект, можно использовать перекрестные элементы или границы класса или и то, и другое, такой подход не рекомендуется.
Pulse, PulseAll и Wait
Когда поток, содержащий вызовы блокировки, снимается Wait и поток добавляется в очередь ожидания синхронизированного объекта. Первый поток в очереди готовности, если таковой имеется, получает блокировку и вводит критическую секцию. Поток, вызываемый, Wait перемещается из очереди ожидания в очередь готовности, если Monitor.Pulse Monitor.PulseAll метод или вызывается потоком, который владеет блокировкой (для перемещения, поток должен находиться в заголовке очереди ожидания). WaitМетод возвращает, когда вызывающий поток снова получает блокировку.
Мониторы и дескрипторы ожидания
Важно отметить различие между использованием Monitor класса и WaitHandle объектов.
MonitorКласс является полностью управляемым, полным переносимым и может быть более эффективным с точки зрения требований к ресурсам операционной системы.
Объекты WaitHandle представляют объекты ожидания операционной системы, удобны для синхронизации между управляемым и неуправляемым кодом и предоставляют некоторые расширенные функции операционной системы, например возможность ожидания сразу нескольких объектов.
Свойства
Возвращает значение, указывающее, сколько раз возникало состязание при попытке установить блокировку монитора.
Методы
Получает эксклюзивную блокировку указанного объекта.
Получает монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.
Освобождает эксклюзивную блокировку указанного объекта.
Определяет, содержит ли текущий поток блокировку указанного объекта.
Уведомляет поток в очереди готовности об изменении состояния объекта с блокировкой.
Уведомляет все ожидающие потоки об изменении состояния объекта.
Пытается получить эксклюзивную блокировку указанного объекта.
Пытается получить монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.
Пытается получить эксклюзивную блокировку указанного объекта на заданное количество миллисекунд.
В течение заданного количества миллисекунд пытается получить монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.
Пытается получить эксклюзивную блокировку указанного объекта в течение заданного количества времени.
В течение заданного периода времени пытается получить монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.
Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова.
Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова. Если указанные временные интервалы истекают, поток встает в очередь готовности.
Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова. Если указанные временные интервалы истекают, поток встает в очередь готовности. Этот метод также указывает на выход из области синхронизации для контекста (если она находится в синхронизированном контексте) до ожидания и ее повторное получение впоследствии.
Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова. Если указанные временные интервалы истекают, поток встает в очередь готовности.
Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова. Если указанные временные интервалы истекают, поток встает в очередь готовности. Дополнительно выходит из синхронизированного домена для синхронизации контекста до ожидания и получает домен впоследствии.