IT • archiv

rus / eng | Логин | Комментарий к колонке | Печать | Почта | Клуб




Колонки


Умри, замри, воскресни...

 
( Андрей Терешко )

Нити или треды (threads). Работать с ними трудно, понять их работу ещё труднее, но, вошедши во вкус — за уши не оттянешь. Хотя есть программисты, считающие, что надо так проектировать программу, чтобы нити были нужны как можно реже. А если ещё обойтись и без объектов, без ООП — то, вообще, будет хорошо. И, надо признаться, поначалу, когда пытаешься отладить программу со многими нитями, то желание все бросить и взять обыкновенный, простой язык, типа …(Вы поняли какой), возникало у меня неоднократно — особенно это касалось взаимодействия между тредами.

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

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

И вот, в таком серпентарии кипит жизнь: змеи-треды постоянно передвигаются по клеткам за мышами или даже достают мышей вне клеток (свободные мыши вне клеток-методов — это public поля объекта, если они есть, конечно). И для чего это я все придумал? Сейчас будет разъяснение.

Кроме клеток и мышей в этой комнате-объекте находится мешок, куда змеи-треды вползают, чтобы "дождаться извещения о событии". В этот мешок змеи-треды могут попасть только из клеток-методов, отмеченных словом synchronized. Из остальных клеток — ну ни как! Так что, если змея-тред хочет "впасть в ожидание извещения о событии" (для того, чтобы дождаться чего-либо, не тратя попусту жизненные силы на постоянную проверку свершения какого либо события), то она должна попасть в клетку-метод synchronized, а оттуда уже — в мешок, где, возможно, уже находятся другие "ждущие извещения о событии" змеи-треды.

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

Змея-тред может находиться в нескольких состояниях:

Первое — активное состояние — нескончаемый поиск и пожирание белых мышей.

Второе — "общая очередь ожидающих доступа для одного объекта" (не путать со "спячкой" и "ожиданием события"!) — на доступ в одну из клеток-методов-synchronized. Из этого состояния змею-тред может вывести только попадание в клетку-synchronized и ничто другое! Если какая нибудь змея-тред займет любую из клеток-synchronized объекта "навечно" (например, по тем или иным причинам, войдет в "вечный" цикл), то остальные змеи-треды, стоящие в очередь на доступ в любую из клеток- synchronized "навечно" и будут ждать. Я не знаю методов и способов, способных заставить змею-тред покинуть эту "очередь ожидающих доступа", кроме как впустить ее в клетку-метод-synchronized. Или вызвать Deprecated метод треда stop (бр-р).

Третье состояние — "ожидание извещения о событии", в которое змея-тред впадает, когда вызывает метод wait() для объекта. В отличии от "очереди ожидающих доступа", змея-тред может выйти из состояния "ожидания извещения о событии" двумя способами: либо получив извещение об событии методом notify (notifyAll), либо по истечении времени "ожидания извещения о событии", которое она же себе и установила, вызвав метод wait(long timeout).

И другие состояния — состояние "сна" (не путь с "ожиданием доступа"), вызывается методом треда sleep(long millis); состояние приостановки треда, вызывается Deprecated методом треда suspend(); состояние ожидания завершения другого треда, вызывается методом треда join() или join(long millis).

Как только змея-тред забралась в мешок "ожидающих извещения о событии", то тем самым клетка-synchronized освобождается и доступ ко всем клеткам-synchronized объекта становится свободным для тредов все еще стоящих в "очереди ожидающих доступа".

Любая(!) змея-тред, находясь в любой(!) клетке-synchronized объекта (и только в клетке типа synchronized), может "укусить", то есть известить о событии либо одну змею-тред из мешка (ту, которая дольше всех "ожидает извещения о событии"), вызвав метод notify(), либо может известить о событии всех змей-тредов "ожидающих извещения о событии" в мешке — методом notifyAll().

Змеи-треды, извещенные о событии, вытряхиваются и вываливаюся из "мешка-ожидания", но стоп(!), они еще не могут начать активную "жизнь". Известить всех о событии — это еще не значит перевести их в активное состояние! Все извещенные змеи-треды сразу же попадают в "очередь ожидающих доступа" — так как, раз они стали ожидать события в клетке-synchronized, то должны(!) в клетке-synchronized и получить известие о событии(а то где?). А так как в любой из клеток-synchronized в одно и то же время может находится только одна(!) змея-тред, то все получившие известие о событии неизбежно попадают в "очередь ожидающих доступа" к клетке-synchronized (если эта очередь, конечно существует, то есть, если получили известие о событии сразу несколько змей-тредов или несколько змей-тредов уже находятся в этой очереди).

Даже, если только одна змея-тред будет извещена о событии (методом notify()), то вернуться к активному состоянию (а для этого надо обязательно(!) попасть в клетку-synchronized) ей может помешать уже имеющаяся "очередь ожидающих доступа" в клетки-synchronized. Точно так, как проснувшись по звонку будильника, нам мешает начать полноценную жизнь… — очередь в ванную.

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

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

Имеется возможность закрыть доступ другим змеям-тредам в клетки-методы-synchronized объекта, не заходя ни в одну из них(!). Для этого змея-тред должна использовать оператор synchronized( ссылка на объект) {…}. Использование этого оператора фактически приводит к запиранию всех(!) клеток-методов-synchronized объекта, до тех пор, пока змея-тред не покинет блок-кода оператора synchronized.

Естественно, использование оператора synchronized () {…} (не путать с атрибутом метода!), не приводит к полному "блокированию объекта", ибо доступ к public полям объекта всегда(!) свободен, также как и вызов методов объекта не имеющих атрибута synchronized. То есть, использование оператора synchronized(){…} влияет только (и только!) на доступ к методам объекта с атрибутом synchronized.

Вообще, термин "блокирования объекта" надо употреблять с пониманием, ибо заблокировать можно только тред, путем блокирования доступа к методам объекта имеющих атрибут synchronized (и только к этим методам!). Или, иначе говоря, заблокировать можно доступ для треда к методу-synchronized. Все остальные методы объекта, не имеющие атрибут synchronized, а также поля объекта, всегда доступны (в пределах описанных для них атрибутов, типа  public или private), не зависимо ни от каких манипуляций!

Однако, если, говоря "объект заблокирован", имеют в виду только и только(!) блокировку доступа тредов к методам-synchronized этого объекта, то вполне допустимо и "блокировать объект" — главное, чтобы Вы понимали и Вас понимали — о чем идет речь. То есть, когда говорят: "дом заблокирован", то подразумевают, что "доступ в дом через эту дверь заблокирован". Но это еще не значит, что в дом нельзя попасть через окно по пожарной лестнице, если, конечно, в доме есть окна без решеток.

Использование термина "блокировать объект", вместо правильного "блокирования доступа к методам объекта имеющих атрибут synchronized" — на практике оказывается вполне допустимым, приемлемым и повсеместно применяемым. Приемлемо именно из-за краткости.

Если программист-разработчик класса, не положил мешок для "ожидающих извещения о событии" в какой-либо из клеток-методов-synchronized, то всегда можно любому(!) объекту "навесить" этот мешок . Не имеет значения — есть ли у этого объекта методы с атрибутом synchronized или нет. Для этого надо использовать оператор synchronized(ссылка на объект){…} , в блок-коде которого применить метод wait():

Object myObject = …
…
synchronized(myObject) {
   …
   myObject.wait();
   …
}

Аналогично, чтобы известить всех ожидающих события в мешке:

synchronized(myObject) {
   …
   myObject.notifyAll();
   …
}

Таким образом, даже для объекта, у которого нет методов с атрибутом synchronized можно сказать — "объект заблокирован" оператором synchronized. Но, по большому счету, блокировать в таком объекте — нечего, ведь методов с атрибутом  synchronized нет! И "блокировка" оператором нужна только для того, чтобы влезть в мешок тредов "ожидающих извещения о событии" или покинуть этот мешок — поскольку только мешок и находится у "блокируемого объекта".

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

Я надеюсь, что читателю, запутавшемуся в wait-notify, поможет моя модель "серпентария" в решении его проблем — ведь главное, чтобы Вы меня понимали.




Справка | Условия Copyright © 1999 — 2010, IT • archiv.
В начало | Логин | Комментарий к колонке | Поиск | Почта