Что такое монитор как монитор реализован в java

Мютекс и монитор

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

Мютекс – это специальный объект для синхронизации нитей/процессов. Он может принимать два состояния – занят и свободен. Если упростить, то мютекс – это boolean-переменная, которая принимает два значения: занят(true) и свободен(false).

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

— Т.е. мютекс – это как табличка на двери – занят/свободен?

— Да. И такой мютекс прикреплен к каждому объекту в Java. Прямой доступ к мютексу есть только у Java-машины. От программиста он скрыт.

— А как же тогда его использовать?

— А работать с мютексом в Java можно посредством монитора.

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

В Java монитор реализован с помощью ключевого слова synchronized.

Когда ты пишешь блок synchronized, то компилятор Java заменяет его тремя кусками кода:

1) В начале блока synchronized добавляется код, который отмечает мютекс как занятый.

2) В конце блока synchronized добавляется код, который отмечает мютекс как свободный.

3) Перед блоком synchronized добавляется код, который смотрит, если мютекс занят – то нить должна ждать его освобождения.

Вот как это примерно работает:

КодКак это работаетОписание
Нить спит, пока мютекс занят
(выйдем из цикла, когда мютекс освободится) Помечаем мютекс занятым

Помечаем мютекс свободным

На самом деле логика там другая и сложнее, но это уже детали.

— А можно подробности?

— Пока не научишься правильно этим пользоваться – в детали лезть нет смысла.

Пару уровней назад я дала тебе список всех состояний нити со стрелками переходов и списком методов, которые влияют на это состояние. Ты много запомнил?

— Не очень. Просто все быстро забывается…

— Чем меньше практики, тем меньше пользы и от теории.

Вот уровню к 40 научишься всем этим пользоваться, и я тебе объясню, как все устроено на самом деле. А пока — учись просто правильно всем пользоваться. Ясно?

Источник

BestProg

Синхронизация. Монитор. Общие понятия. Ключевое слово synchronized

Содержание

Поиск на других ресурсах:

1. Понятие синхронизации между потоками. Необходимость применения синхронизации. Монитор

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

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

Синхронизация базируется на использовании мониторов. Монитор — это объект, который используется для взаимоисключающей блокировки. Взаимоисключающая блокировка позволяет владеть монитором только одному объекту-потоку. Каждый объект-поток имеет собственный, неявно связанный с ним, монитор.

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

Монитором может обладать только один поток. Если поток (объект) обладает монитором, то он при необходимости может повторно войти в него.

В языке Java синхронизация применяется к целым методам или фрагментам кода. Исходя из этого существует два способа синхронизации программного кода:

Модификатор доступа synchronized применяется при объявлении синхронизированного метода и имеет следующую общую форму:

3. Оператор synchronized() < >. Общая форма

Общая форма оператора synchronized () следующая:

4. Пример, демонстрирующий синхронизированный доступ к общему методу из трех разных потоков. Применение модификатора доступа synchronized

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

Результат выполнения программы

Если в вышеприведенном примере перед методом Get() класса Array5 убрать ключевое слово synchronized

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

5. Пример использования оператора synchronized() <> для синхронизированного доступа к общему ресурсу

После внесенных изменений, сокращенный код программы будет следующий:

Источник

А как же всё-таки работает многопоточность? Часть I: синхронизация

Что такое монитор как монитор реализован в java. Смотреть фото Что такое монитор как монитор реализован в java. Смотреть картинку Что такое монитор как монитор реализован в java. Картинка про Что такое монитор как монитор реализован в java. Фото Что такое монитор как монитор реализован в java(пост из серии «я склонировал себе исходники 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 за то, что они (вы|про)читали статью перед публикацией, убедившись тем самым, что я не принесу в массы вместо света какую-то бредовню, наполненную тупыми шутками и очепатками.

Источник

Что такое монитор как монитор реализован в java

Расскажите о модели памяти Java?

Модель памяти Java (Java Memory Model, JMM) описывает поведение потоков в среде исполнения Java. Это часть семантики языка Java, набор правил, описывающий выполнение многопоточных программ и правил, по которым потоки могут взаимодействовать друг с другом посредством основной памяти.

Существует несколько основных правил для отношения happens-before:

Можно выделить несколько основных областей, имеющих отношение к модели памяти:

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

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

При входе в synchronized метод или блок поток обновляет содержимое локальной памяти, а при выходе из synchronized метода или блока поток записывает изменения, сделанные в локальной памяти, в главную. Такое поведение synchronized методов и блоков следует из правил для отношения «происходит раньше»: так как все операции с памятью происходят раньше освобождения монитора и освобождение монитора происходит раньше захвата монитора, то все операции с памятью, которые были сделаны потоком до выхода из synchronized блока должны быть видны любому потоку, который входит в synchronized блок для того же самого монитора. Очень важно, что это правило работает только в том случае, если потоки синхронизируются, используя один и тот же монитор!

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

Есть одна проблема, связанная с final полями: реализация разрешает менять значения таких полей после создания объекта (это может быть сделано, например, с использованием механизма reflection). Если значение final поля—константа, чьё значение известно на момент компиляции, изменения такого поля могут не иметь эффекта, так-как обращения к этой переменной могли быть заменены компилятором на константу. Также спецификация разрешает другие оптимизации, связанные с final полями, например, операции чтения final переменной могут быть переупорядочены с операциями, которые потенциально могут изменить такую переменную. Так что рекомендуется изменять final поля объекта только внутри конструктора, в противном случае поведение не специфицировано.

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

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

Что такое «потокобезопасность»?

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

В чём разница между «конкуренцией» и «параллелизмом»?

Конкуренция — это способ одновременного решения множества задач.

Параллелизм — это способ выполнения разных частей одной задачи.

Что такое «кооперативная многозадачность»? Какой тип многозадачности использует Java? Чем обусловлен этот выбор?

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

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

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

Что такое ordering, as-if-serial semantics, sequential consistency, visibility, atomicity, happens-before, mutual exclusion, safe publication?

ordering механизм, который определяет, когда один поток может увидеть out-of-order (неверный) порядок исполнения инструкций другого потока. CPU для повышения производительности может переупорядочивать процессорные инструкции и выполнять их в произвольном порядке до тех пор пока для потока внутри не будет видно никаких отличий. Гарантия, предоставляемая этим механизмом, называется as-if-serial semantics.

visibility определяет, когда действия в одном потоке становятся видны из другого потока.

atomicity — атомарность операций. Атомарная операция выглядит единой и неделимой командой процессора, которая может быть или уже выполненной или ещё невыполненной.

Чем отличается процесс от потока?

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

Для каждого процесса ОС создает так называемое «виртуальное адресное пространство», к которому процесс имеет прямой доступ. Это пространство принадлежит процессу, содержит только его данные и находится в полном его распоряжении. Операционная система же отвечает за то, как виртуальное пространство процесса проецируется на физическую память.

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

Что такое «зелёные потоки» и есть ли они в Java?

Виртуальная машина Java берёт на себя заботу о переключении между разными green threads, а сама машина работает как один поток ОС. Это даёт несколько преимуществ. Потоки ОС относительно дороги в большинстве POSIX-систем. Кроме того, переключение между native threads гораздо медленнее, чем между green threads.

Это всё означает, что в некоторых ситуациях green threads гораздо выгоднее, чем native threads. Система может поддерживать гораздо большее количество green threads, чем потоков OС. Например, гораздо практичнее запускать новый green thread для нового HTTP-соединения к веб-серверу, вместо создания нового native thread.

Однако есть и недостатки. Самый большой заключается в том, что вы не можете исполнять два потока одновременно. Поскольку существует только один native thread, только он и вызывается планировщиком ОС. Даже если у вас несколько процессоров и несколько green threads, только один процессор может вызывать green thread. И всё потому, что с точки зрения планировщика заданий ОС всё это выглядит одним потоком.

Начиная с версии 1.2 Java поддерживает native threads, и с тех пор они используются по умолчанию.

Каким образом можно создать поток?

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

Как принудительно запустить поток?

Никак. В Java не существует абсолютно никакого способа принудительного запуска потока. Это контролируется JVM и Java не предоставляет никакого API для управления этим процессом.

Что такое «монитор» в Java?

Монитор, мьютекс (mutex) – это средство обеспечения контроля за доступом к ресурсу. У монитора может быть максимум один владелец в каждый текущий момент времени. Следовательно, если кто-то использует ресурс и захватил монитор для обеспечения единоличного доступа, то другой, желающий использовать тот же ресурс, должен подождать освобождения монитора, захватить его и только потом начать использовать ресурс.

Удобно представлять монитор как id захватившего его объекта. Если этот id равен 0 – ресурс свободен. Если не 0 – ресурс занят. Можно встать в очередь и ждать его освобождения.

Дайте определение понятию «синхронизация».

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

Какие существуют способы синхронизации в Java?

В каких состояниях может находиться поток?

Потоки могут находиться в одном из следующих состояний:

Можно ли создавать новые экземпляры класса, пока выполняется static synchronized метод?

Да, можно создавать новые экземпляры класса, так как статические поля не принадлежат к экземплярам класса.

Зачем может быть нужен private мьютекс?

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

Почему методы wait() и notify() вызываются только в синхронизированном блоке?

Чем отличается работа метода wait() с параметром и без параметра?

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

Метод sleep() вызывает засыпание текущего потока на заданное время, состояние изменяется с работающий (running) на ожидающий (waiting).

Когда поток вызывает join() для другого потока, текущий работающий поток будет ждать, пока другой поток, к которому он присоединяется, не будет завершён:

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

livelock – тип взаимной блокировки, при котором несколько потоков выполняют бесполезную работу, попадая в зацикленность при попытке получения каких-либо ресурсов. При этом их состояния постоянно изменяются в зависимости друг от друга. Фактической ошибки не возникает, но КПД системы падает до 0. Часто возникает в результате попыток предотвращения deadlock.

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

Как проверить, удерживает ли поток монитор определённого ресурса?

На каком объекте происходит синхронизация при вызове static synchronized метода?

В чём различия между volatile и Atomic переменными?

Что значит «приоритет потока»?

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

Можно ли сделать основной поток программы демоном?

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

Что значит «усыпить» поток?

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

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

CyclicBarrier реализует шаблон синхронизации «Барьер». Циклический барьер является точкой синхронизации, в которой указанное количество параллельных потоков встречается и блокируется. Как только все потоки прибыли, выполняется опционное действие (или не выполняется, если барьер был инициализирован без него), и, после того, как оно выполнено, барьер ломается и ожидающие потоки «освобождаются». В конструкторы барьера CyclicBarrier(int parties) и CyclicBarrier(int parties, Runnable barrierAction) обязательно передается количество сторон, которые должны «встретиться», и, опционально, действие, которое должно произойти, когда стороны встретились, но перед тем когда они будут «отпущены».

Что такое race condition?

Существует ли способ решения проблемы race condition?

Распространённые способы решения:

Очевидных способов выявления и исправления состояний гонки не существует. Лучший способ избавиться от гонок — правильное проектирование многозадачной системы.

Как остановить поток?

Схема действия при этом получается следующей:

Что происходит, когда в потоке выбрасывается исключение?

Механизм прерывания работы потока в Java реализован с использованием внутреннего флага, известного как статус прерывания. Прерывание потока вызовом Thread.interrupt() устанавливает этот флаг. Методы Thread.interrupted() и isInterrupted() позволяют проверить, является ли поток прерванным.

Нестатический метод isInterrupted() используется одним потоком для проверки статуса прерывания у другого потока, не изменяя флаг прерывания.

Что такое «пул потоков»?

Создание потока является затратной по времени и ресурсам операцией. Количество потоков, которое может быть запущено в рамках одного процесса также ограниченно. Чтобы избежать этих проблем и в целом управлять множеством потоков более эффективно в Java был реализован механизм пула потоков (thread pool), который создаётся во время запуска приложения и в дальнейшем потоки для обработки запросов берутся и переиспользуются уже из него. Таким образом, появляется возможность не терять потоки, сбалансировать приложение по количеству потоков и частоте их создания.

Методы Executors для создания пулов:

Какого размера должен быть пул потоков?

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

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

Что будет, если очередь пула потоков уже заполнена, но подаётся новая задача?

В чём заключается различие между методами submit() и execute() у пула потоков?

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

execute(Runnable command) определён в интерфейсе Executor и выполняет поданную задачу и ничего не возвращает.

В чем заключаются различия между cтеком (stack) и кучей (heap) с точки зрения многопоточности?

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

Как поделиться данными между двумя потоками?

Какой параметр запуска JVM используется для контроля размера стека потока?

Как получить дамп потока?

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

Метод на основе событий:

Что такое ThreadLocal-переменная?

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

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

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

Что такое «блокирующий метод»?

Блокирующий метод – метод, который блокируется, до тех пор, пока задание не выполнится, например метод accept() у ServerSocket блокируется в ожидании подключения клиента. Здесь блокирование означает, что контроль не вернётся к вызывающему методу до тех пор, пока не выполнится задание. Так же существуют асинхронные или неблокирующиеся методы, которые могут завершится до выполнения задачи.

Что такое «фреймворк Fork/Join»?

Решение всех подзадач (в т.ч. и само разбиение на подзадачи) происходит параллельно.

Для решения некоторых задач этап Join не требуется. Например, для параллельного QuickSort — массив рекурсивно делится на всё меньшие и меньшие диапазоны, пока не вырождается в тривиальный случай из 1 элемента. Хотя в некотором смысле Join будет необходим и тут, т.к. всё равно остаётся необходимость дождаться пока не закончится выполнение всех подзадач.

Ещё одно замечательное преимущество этого фреймворка заключается в том, что он использует work-stealing алгоритм: потоки, которые завершили выполнение собственных подзадач, могут «украсть» подзадачи у других потоков, которые всё ещё заняты.

Semaphore – это новый тип синхронизатора: семафор со счётчиком, реализующий шаблон синхронизации Семафор. Доступ управляется с помощью счётчика: изначальное значение счётчика задаётся в конструкторе при создании синхронизатора, когда поток заходит в заданный блок кода, то значение счётчика уменьшается на единицу, когда поток его покидает, то увеличивается. Если значение счётчика равно нулю, то текущий поток блокируется, пока кто-нибудь не выйдет из защищаемого блока. Semaphore используется для защиты дорогих ресурсов, которые доступны в ограниченном количестве, например подключение к базе данных в пуле.

Что такое double checked locking Singleton?

Следует заметить, что требование volatile обязательно. Проблема Double Checked Lock заключается в модели памяти Java, точнее в порядке создания объектов, когда возможна ситуация, при которой другой поток может получить и начать использовать (на основании условия, что указатель не нулевой) не полностью сконструированный объект. Хотя эта проблема была частично решена в JDK 1.5, однако рекомендация использовать volatile для Double Cheсked Lock остаётся в силе.

Как создать потокобезопасный Singleton?

Чем полезны неизменяемые объекты?

Что такое busy spin?

Перечислите принципы, которым вы следуете в многопоточном программировании?

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

Какое из следующих утверждений о потоках неверно?

Вызов метода start() дважды для одного и того же объекта Thread приведёт к генерированию исключения IllegalThreadStateException во время выполнения, следовательно, утверждение 1 верно. Утверждение 2 верно, так как порядок, в котором выполняются потоки, определяется Планировщиком потоков, независимо от того, какой поток запущен первым. Утверждение 4 верно, так как поток не освободит блокировки, которые он держит, когда он переходит в состояние Ожидания.

Даны 3 потока Т1, Т2 и Т3? Как реализовать выполнение в последовательности Т1, Т2, Т3?

Напишите минимальный неблокирующий стек (всего два метода — push() и pop() ).

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *