Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Java_sp.doc
Скачиваний:
48
Добавлен:
29.10.2018
Размер:
1 Мб
Скачать

8. Параллельные процессы. Управление памятью потока.

Потоки выполнения отличаются от процессов тем, что находятся в адресном пространстве своего родительского процесса. Они выполняются параллельно (псевдопараллельно), но, в отличие от процессов, легко могут обмениваться данными в пределах общего виртуального адресного пространства. То есть у них могут иметься общие переменные, в том числе – массивы и объекты.

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

Любая программа Java неявно использует потоки выполнения. В главном потоке виртуальная Java-машина (JVM) запускает метод main приложения, а также все методы, вызываемые из него. Главному потоку автоматически дается имя "main". Кроме главного потока в фоновом режиме (с малым приоритетом) запускается дочерний поток, занимающийся сборкой мусора. Виртуальная Java-машина автоматически стартует при запуске на компьютере хотя бы одного приложения Java, и завершает работу в случае, когда у нее на выполнении остаются только потоки-демоны.

В Java каждый поток выполнения рассматривается как объект. Но интересно то, что в Java каждый объект, даже не имеющий никакого отношения к классу Thread, может работать в условиях многопоточности, поскольку в классе Object определены методы объектов, предназначенные для взаимодействия объектов в таких условиях. Это notify(), notifyAll(), wait(), wait(timeout) – "оповестить", "оповестить всех","ждать", "ждать до истечения таймаута".

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

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

Хранение переменных в памяти

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

Для каждого потока создается его собственная рабочая память (working memory), в которую перед использованием копируются значения всех переменных. Рассмотрим основные операции, доступные для потоков при работе с памятью:

  • use – чтение значения переменной из рабочей памяти потока;

  • assign – запись значения переменной в рабочую память потока;

  • read – получение значения переменной из основного хранилища;

  • load – сохранение значения переменной, прочитанного из основного хранилища, в рабочей памяти;

  • store – передача значения переменной из рабочей памяти в основное хранилище для дальнейшего хранения;

  • write – сохраняет в основном хранилище значение переменной, переданной командой store.

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

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

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

Последовательность команд подчиняется следующим правилам:

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

  • все действия, выполняемые с одной переменной в основном хранилище памяти, строго упорядочены, т.е. следуют одно за другим.

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]