CPJ. Objects and Concurrency
по мотивам «Concurrent Programming in Java»
- 1.2 Объекты и параллелизм
1.2 Объекты и параллелизм
Существует множество способов описания объектов, параллелизма и их взаимосвязи. В этом разделе данные понятия рассмотрены с разных сторон: формальной (definitional), системной, стилистической, и с точки зрения моделирования. Все это должно помочь создать понятийную основу конкурентного ООП (concurrent object-oriented programming).
Параллелизм
Как и большинство компьютерных терминов, термин параллелизм (concurrency) довольно сложно определить однозначно. Неформально, конкурентная (concurrent) программа - это такая программа, которая выполняет более, чем одно действие одновременно. Например, веб-браузер может одновременно выполнять HTTP GET-запрос для получения HTML-страницы, проигрывать аудио-клип, показывать байты полученные для какого-то изображения, и вести консультативный (advisory) диалог с пользователем. Однако, иногда одновременность является иллюзией. На некоторых компьютерных системах эти действия действительно могут выполняться на разных ЦПУ. Но на других системах все они выполняются на единственном ЦПУ, который переключается между задачами настолько быстро, что кажется, что они выполняются одновременно или, по крайней мере, недетерминированно чередующимися (nondeterministically interleaved), для человеческого внимания.
Более точное, хотя и не очень интересное, определение параллелизма можно сформулировать так: JVM и ОС обеспечивают отображение видимой одновременности на физический параллелизм (с помощью нескольких ЦПУ), или, при его отсутствии, позволяя независимым действиям выполняться параллельно, при возможности и необходимости, с помощью разделения времени. Конкурентное программирование заключается в использовании программных конструкций, которые отображаются как описано выше. Конкурентное программирование в Java подразумевает использование для этих целей конструкций языка в противоположность конструкциям системного уровня, используемым для создания новых процессов операционной системы. Здесь и далее сказанное ограничивается конструкциями выполняющимися на единственной JVM, в отличие от распределённых вычислений с использованием, например, RMI, в случае чего в работу вовлечено несколько JVM располагающихся на нескольких компьютерных системах.
Параллелизм и причины его использования проще понять рассмотрев природу некоторых распространенных типов конкурентных приложений:
-
Веб-сервисы (web services). Большинство веб-сервисов использующих сокеты (socket-based), (например: HTTP-демоны, движки сервлетов, и сервера приложений) являются многопоточными. Обычно, основная причина поддержки множества параллельных соединений заключается в обеспечении того, что новым входящим соединениям не требуется ожидать завершения других. Это, в общем, минимизирует задержки (latencies) сервиса и повышает доступность (availability).
-
Числодробилки. Многие, требующие большого объема вычислений, задачи могут быть распараллелены и, таким образом, выполнены более быстро, при наличии нескольких ЦПУ. В данном случае целью является повышение скорости обработки за счет использования параллелизма.
-
I/O операции. Даже в формально (nominally) последовательном компьютере устройства, выполняющие чтение и запись на диск, по сети, и т.п., работают независимо от ЦПУ. Многопоточные программы могут с пользой использовать время, иначе потраченное на ожидание завершения таких операций, и, таким образом, обеспечить более эффективное использование ресурсов компьютера.
-
Моделирование. Конкурентные программы могут имитировать физические объекты с независимым автономным поведением, чего трудно достичь в строго последовательных программах.
-
Графические приложения. Несмотря на то, что большинство пользовательских интерфейсов намеренно однопоточны, обычно они взаимодействуют с многопоточными сервисами. Параллелизм позволяет пользовательским элементам управления быть отзывчивыми даже в ходе продолжительных операций.
-
Компонентное ПО. Крупные компоненты ПО (такие как редакторы макетов) могут создавать внутренние потоки для вспомогательных целей, обеспечения поддержки мультимедиа, достижения большей автономности или улучшения производительности.
-
Мобильный код. Такие фреймворки как пакет
java.applet
исполняют загруженный код в отдельных потоках, что представляет собой одну из сторон политик изоляции, мониторинга и контроля неизвестного кода. -
Встроенные (embedded) системы. Большинство программ, запускаемых на небольших специально предназначенных устройствах, осуществляют контроль в режиме реального времени. Каждый из множества компонентов непрерывно реагирует на внешние входные данные от сенсоров и других устройств, и возвращает определенный отклик в пределах заданного времени. Как определено в Java Language Specification, платформа не поддерживает жесткий режим реального времени при котором корректность системы зависит от действий выполненных до заданного предельного срока (deadline). Определенные системы времени выполнения (run-time systems) могут предоставить бОльшую гарантию, требуемую критически важными системами жесткого реального времени. Но все реализации JVM поддерживают контроль мягкого реального времени (soft real-time control), при котором своевременность и производительность трактуются как задачи качества обслуживания (quality-of-service), а не корректности. Это отражает цели переносимости, которые обеспечивают возможность реализации JVM на современных вероятностных, многоцелевых аппаратных платформах и системном программном обеспечении.
Программные конструкции параллелизма
Потоки являются одной из нескольких конструкций для параллельного выполнения кода. Идея генерирования новой задачи может быть спроецирована на одну из нескольких абстракций в соответствии с крупностью разбиения, отражающей компромисс между автономностью и накладными расходами. Основанная на использовании потоков архитектура не всегда является лучшим решением конкретной задачи требующей параллелизма. Выбор одной из, обсуждаемых ниже, альтернатив может предоставить различный уровень безопасности, защиты (protection), отказоустойчивости и административного контроля с бОльшим или меньшим уровнем накладных расходов. Различия между этими вариантами (и поддерживающими их программными конструкциями) влияют на стратегию архитектуры более, чем окружающие их детали.
Компьютерные системы
Если у вас есть большой парк компьютерных систем, то вы можете сопоставить каждую логическую единицу исполнения отдельному компьютеру. Каждая компьютерная система может быть однопроцессорной, многопроцессорной или даже кластером машин, управляемым как одно целое и имеющим общую операционную систему. Этот вариант обеспечивает неограниченную автономность и независимость. Каждая система может администрироваться и контролироваться отдельно от других.
Однако, формирование, обнаружение, повторение, и пересылка сообщений между такими сущностями может быть накладной; отсутствует возможность общего пользования локальными ресурсами; решение проблем связанных с именованием (naming), безопасностью, отказоустойчивостью, восстановлением, и достижимостью (reachability) является относительно сложной задачей в сравнение с проблемами конкурентных программ. Поэтому данный вариант обычно применим только для тех частей системы, которые по своему содержанию требуют распределенного решения. И даже в этом случае все, кроме самых крошечных встроенных компьютерных устройств, исполняют более одного процесса.
Процессы
Процесс - это абстракция ОС, которая позволяет одной компьютерной системе поддерживать несколько единиц выполнения. Каждый процесс, обычно, представляет собой отдельную запущенную программу, например, выполняющуюся JVM. Понятие процесс, как и компьютерная система, являются логическими абстракциями, не физическими. Например, связь между процессами и ЦПУ может меняться динамически.
ОС гарантируют определенную степень независимости, отсутствие интерференций, и обеспечение безопасности среди параллельно-выполняющихся процессов. Процессам, в общем случае, запрещен доступ к памяти других процессов (хотя имеются некоторые исключения) и, вместо этого, они должны взаимодействовать с помощью средств межпроцессной коммуникации, например конвейера (pipe). Большинство систем дают обещания о, по крайней мере, наилучшей возможной диспетчеризации процессов. Это практически всегда подразумевает вытеснение с квантованием времени, при котором процессы периодически приостанавливаются, чтобы дать другим процессам шанс быть выполнеными.
Накладные расходы создания, управления и взаимодействия между процессами могут быть намного меньше, чем в распределенной системе. Однако, поскольку процессы используют общие компьютерные ресурсы (ЦПУ, ОЗУ, каналы ввода-вывода, и т.д.), они менее автономны. Например, вызванный одним процессом сбой машины приведет к остановке всех процессов.
Потоки
Потоки различного вида ещё больше жертвуют автономностью ради, в определенной степени, меньших накладных расходов. Основными отличиями являются:
-
Разделяемые ресурсы. Потоки могут иметь общий доступ к памяти, открытым файлам, и другим, ассоциированным с процессом, ресурсам. Потоки в Java могут могут разделять все эти ресурсы. Некоторые ОС также поддерживают промежуточные конструкции, например, облегченные процессы (lightweight processes) и потоки ядра (kernel threads), которые разделяют только некоторые ресурсы, и делают это только при явном запросе или других ограничениях.
-
Диспетчеризация. Гарантии независимости могут быть смягчены для обеспечения более дешевой политики диспетчеризации. Одной крайностью является рассмотрение всех потоков в виде однопоточного процесса, в случае чего они могут кооперативно соперничать друг с другом таким образом, что в отдельный момент времени запущенным будет только один поток, не давая, тем самым, другим потокам шанс быть запущенными до момента его блокировки. Другой крайностью является обеспечение планировщиком возможности всем потокам в системе напрямую соревноваться друг с другом с помощью вытесняющих правил диспетчеризации. Потоки в Java могут планироваться любым, из приведенных выше, способом, либо чем-то средним между ними.
-
Коммуникация. Системы взаимодействуют через проводные или беспроводные каналы связи используя, например, сокеты. Процессы, также, могут коммуницировать таким образом, но поддерживают более легкие механизмы, такие как конвейеры (pipes) и сигналы межпроцессного взаимодействия (interprocess signalling facilities). Потоки могут использовать эти возможности, и, вдобавок, другие, менее ресурсозатратные стратегии базирующиеся на доступе к памяти доступной в пределах нескольких потоков, и применения синхронизации с использованием памяти, такой как блокировки и механизм ожиданий/уведомлений. Эти конструкции обеспечивают более эффективное взаимодействие, но, иногда, влекут за собой бОльшую сложность и, следовательно, бОльшую вероятность программных ошибок.
Задачи и облегченные фреймворки
Накладные расходы поддержки потоков приемлемы для большого числа приложений, но не всегда отвечают потребностям конкретной задачи. Хотя характеристики производительности и отличаются на различных платформах, все равно накладные расходы на создание потока значительно превышают более дешевый (но наименее независимый) способ запуска блока кода выполнив его напрямую в текущем потоке.
Когда затраты на создание и управление потоками становятся проблемами производительности есть возможность, ещё более жертвуя автономностью, создать собственный облегченный (lighter-weight) фреймворк, накладывающий бОльшие ограничения использования (например, запрещая определенные формы блокирования), или дающий меньше гарантий диспетчеризации, или ограничивающий возможности синхронизации и коммуникации определенным набором конструкций. Эти единицы работы (tasks) могут быть отображены на потоки почти также как и потоки отображаются на процессы и компьютерные системы.
Наиболее привычными облегченными фреймворками являются событийные системы и подсистемы, в которых вызовы запускают, концептуально, асинхронные действия трактуемые в виде событий, которые могут быть помещены в очередь и обработаны фоновыми потоками. Когда это уместно, создание и использование таких фреймворков может улучшить как структуру, так и производительность конкурентных программ. Их использование уменьшает число проблем, препятствующих применению техник параллельного выполнения задач для выражения логически асинхронных действий и логически автономных объектов.
Параллелизм и ООП
Объекты имеют связь с параллелизмом со времен появления обоих. Созданный примерно в 1966 г. язык программирования Simula был как первым ОО языком, так и одним из первых языков поддерживающих параллелизм. Первоначальные ОО и конкурентные конструкции Simula были несколько примитивны и неуклюжи. Например, параллелизм был построен на корутинах (coroutines) - конструкциях, подобных потокам, которые требовали явной передачи программистом управления от одной задачи к другой. После было еще несколько языков предоставляющих как конкурентные так и ОО конструкции. Более того, даже наиболее ранние прототипные версии C++ включали несколько библиотечных классов для поддержки параллелизма. Язык программирования Ada (хотя и не совсем ОО язык в первых версиях) помог вывести конкурентное программирование из мира специализированных, низкоуровневых языков и систем.
ОО дизайн не играл какой-то существенной роли в практике программирования многопоточных систем появившихся в 1970-х. И параллелизм, в свою очередь, не играл особой роли в распространении ООП, начавшемся в 1980-х. Но интерес к ОО параллелизму оставался живым в исследовательских лабораториях и передовых группах разработчиков, и проявился вновь как неотъемлимая часть программирования отчасти благодаря популярности и повсеместности платформы Java.
Конкурентное ООП фактически обладает всеми возможностями любого вида программирования, но особым образом отличается от наиболее привычных видов программирования, о чем рассказывается ниже.
Cеквенциальное ООП (Sequential OO programming)
Конкурентные ОО-программы обычно создаются с применением таких же техник программирования и шаблонов проектирования как и секвенциальные ОО-программы. Но, в действительности, они более сложны. Когда более, чем одна активность имеет место в одно и то же время, выполнение программы недетерминированно. Код может выполняться в неожиданном порядке, т.к. возможен любой допустимый порядок. Поэтому не всегда можно понять конкурентную программу последовательно читая её код. Например, без применения необходимых мер, поле, значение которого устанавливается в первой строке кода, может иметь отличное значение (по причине какой-то другой конкурентной активности) на момент выполнения второй строки кода. Для того чтобы контролировать это и другие виды интерференций обычно требуется более строгий и консервативный взгляд на архитектуру программы.
Событийно-ориентированное программирование
Некоторые техники конкурентного программирования имеют много общего с событийно-ориентированными фреймворками, используемыми в GUI-библиотеках поддерживаемых пакетами java.awt
и java.swing
, и других языках, таких как Tcl/Tk и Visual Basic. В GUI фреймворках такие события как щелчок мыши инкапсулируются в объекты Event
и помещаются в единственную очередь EventQueue
. После эти события вычитываются и обрабатываются одно за другим в единственном событийном цикле (event loop) который, обычно, запущен в отдельном потоке. Этот подход может быть расширен для поддержки дополнительного параллелизма с помощью (помимо других вариантов) создания множества потоков с событийными циклами, каждый из которых параллельно обрабатывает события, или даже обработки каждого события в отдельном потоке. Хотя такой подход открывает новые архитектурные возможности, он также привносит новые проблемы касаемо вмешательства (interference) и координации конкурентных активностей.
Программирование конкурентных систем
Объектно-ориентированное конкурентное программирование отличается от программрования многопоточных систем на таких языках как C, главным образом, за счет поддержки инкапсуляции, модульности, расширяемости, безопасности, и защиты, отсутствующих в C. Вдобавок, поддержка конкурентности в Java реализована не с помощью библиотек, а встроена в сам язык, что устраняет возможность возникновения некоторых распространенных ошибок, и позволяет компилятору автоматически, и безопасно, выполнять определенные оптимизации которые в C было бы необходимо делать вручную.
Хотя конструкции поддержки параллелизма в Java в целом похожи на те, что предоставляет стандартная POSIX-библиотека pthreads и соответствующие, обычно используемые в C, пакеты, существуют важные различия, особенно в деталях реализации ожидания (waiting) и уведомления (notification). Вполне возможно использовать вспомогательные классы, которые ведут себя почти как POSIX процедуры и функции. Но, обычно, вместо этого более продуктивно внести в программу небольшие изменения с целью использования версий операций напрямую поддерживаемых языком.
Другие языки программирования, поддерживающие параллелизм
По существу, все языки, поддерживающие параллелизм, на определенном уровне эквивалентны, но только в том смысле, что все они, по распространенному мнению, не включают правильных конкурентных конструкций. Однако, не так трудно создать программу на одном языке, которая бы выглядела практически эквивалентно на других языках или использовала другие конструкции, предоставляемые пакетами, классами, утилитами, инструментами и стандартами оформления кода имитирующими встроенные возможности других языков. По ходу этой книги рассматриваются конструкции, предоставляющие возможности и стиль программирования систем использующих семафоры (semaphore-based), futures, параллелизм с барьерами (barrier-based parallelism ), CSP (communicating sequential processes) и др. Использование только одного из этих стилей при написании программы является очень хорошей идеей, в случае если этого достаточно. Однако, большинство конкурентных архитектурных решений, шаблонов, фреймворков и систем имеют разнообразное наследие и заимствуют хорошие идеи откуда только возможно.
Объектные модели и отображения
Концепции объектов обычно различаются в секвенциальном и конкурентном ОО-программировании, и даже между различными стилями конкурентного ОО-программирования. Размышления о, лежащей в основе, объектной модели и способе отображения могуть раскрыть природу различий между стилями программирования упомянутыми в предыдущем разделе.
Большинству людей нравится думать о программных объектах как о моделях реальных объектов, представленных с произвольной степенью точности. Понятие реальный конечно же субъективно и часто включает атрибуты имеющие смысл только в рамках вычислительного процесса.
В качестве простого примера рассмотрим UML-диаграмму и фрагмент кода класса WaterTank
:
class WaterTank {
final float capacity;
float currentVolume = 0.0f;
WaterTank overflow;
WaterTank(float cap) { capacity = cap; ... }
void addWater(float amount) throws OverflowException;
void removeWater(float amount) throws UnderflowException;
}
Основной целью является представление, или моделирование, резервуара для воды, используя:
- Атрибуты, такие как
capacity
иcurrentVolume
, которые представлены в виде полей объекта классаWaterTank
. Мы можем выбрать только те атрибуты, которые имеют смысл в данном наборе сценариев использования. Например, хотя реальный резервуар имеет некоторое месторасположение, форму, цвет и т.д., в данном классе отражены только объемы.
-
Инвариантные ограничения состояний (invariant state constraints), такие как факт того, что значение
currentVolume
всегда остаётся между нулем иcapacity
, и что значениеcapacity
не отрицательно и неизменяемо после создания. -
Операции, описывающие поведение, такие как
addWater
иremoveWater
. Выбор операций снова отражает некоторые неявные архитектурные решения, касающиеся точности и степени детализации. Например, мы могли бы решить смоделировать резервуар на уровне вентелей и клапанов, а каждую молекулу воды в виде отдельного объекта, изменяющего своё месторасположение в результате соответствующих операций. -
Связи (и потенциальные связи) с другими объектами, такими как трубы и другие резервуары, и их взаимодействие. Например, переизбыток поступающей, в результате операции
addWater
, воды должен быть отведен в запасной резервуар, связанный с каждым основным резервуаром. -
Предусловия и постусловия для результатов операций, такие как правила задающие невозможность слива воды из пустого резервуара, или добавления воды в полный резервуар не оснащенный дополнительным резервуаром для перелива.
-
Протоколы, ограничивающие как и когда сообщения (запросы операций) будут обработаны. Например, мы можем установить правило по которому только одно из сообщений
addWater
илиremoveWater
может быть обработано в конкретный момент времени, или наоборот, правило утверждающее допустимость сообщенияremoveWater
в ходе операцииaddWater
.
Модели объектов
Класс WaterTank
использует объекты для моделирования действительности. Модели объектов предоставляют правила и фреймворки для определения объектов в более общем виде, включая:
-
Статику (statics). Структура каждого объекта описывается (обычно в виде класса) в терминах внутренних атрибутов (состояние), связей с другими объектами, локальных (внутренних) методов, и методов или портов для получения сообщений от других объектов.
-
Инкапсуляцию. Объекты имеют мембраны отделяющие то, что внутри от того, что снаружи. Внутреннее состояние напрямую может быть изменено только самим объектом (не учитывая возможности языка позволяющие нарушить это правило).
-
Коммуникацию. Объекты взаимодействуют только с помощью передачи сообщений. Объекты отправляют сообщения, которые становятся причиной действий других объектов. Формы этих сообщений могут варьироваться от простого вызова процедуры до передаваемых, с помощью произвольных протоколов, сообщений.
-
Идентификацию. Новые объекты могут создаваться в любое время (в зависимости от ограничений системных ресурсов) любым объектом (в зависимости от прав доступа). После создания каждый объект обладает возможностью уникальной индентификации, сохраняющейся в ходе его существования.
-
Связи. Объект может отправлять сообщения другим объектам если он знает их идентификаторы. Некоторые модели используют идентификаторы каналов вместо, или вдобавок, к идентификаторам объектов. Концептуально, канал - это транспорт для передачи сообщений. Два объекта, которые используют один и тот же канал, могут передавать через него сообщения не зная идентификаторов друг друга. Традиционные ОО-модели и языки используют объектно-ориентированные примитивы для прямого вызова методов, абстракции основанные на работе с каналами для I/O-операций и взаимодействия по сети, и такие конструкции как каналы событий, которые могут использоваться в обоих случаях.
-
Операции. Объекты могут выполнять четыре базовых вида операций:
- прием сообщения;
- изменение внутреннего состояния;
- отправка сообщения;
- создание нового объекта.
Данная абстрактная классификация может быть интерпретирована и уточнена несколькими способами. Например, одним из вариантов реализации объекта WaterTank
может быть создание крошечного специализированного аппаратного устройства, которое бы поддерживало только заданные состояния, инструкции и связи. Но, так как это не книга по архитектуре аппаратного обеспечения, мы ограничимся только вариантами использующими программное обеспечение.
Cеквенциальное отображение (Sequential mappings)
Возможности типичного компьютера общего назначения (ЦПУ, шина, память, I/O-порты) могут быть использованы для чтобы он смог притвориться каким-либо объектом, например WaterTank
. Этого можно достичь загрузив описание WaterTank
(в виде .class
-файла) в JVM. После этого JVM сможет создать пассивное представление сущности и интерпретировать соответствующие операции. Эта стратегия отображения также применима и на уровне ЦПУ, когда операции скомпилированы в машинный код, а не интерпретируются в виде байткода. Это, также, распространяется и на программы включающие множество объектов различных классов, каждый из которых загружается и инициализируется по мере необходимости, благодаря наличию у JVM в любой момент времени записи идентификатора (this
) текущего имитируемого объекта.
Другими словами, JVM сама по себе является объектом, хотя и очень специфичным, и может притворяться любым другим объектом (более формально, она служит в качестве Универсальной Машины Тьюринга). Хотя схожие замечания справедливы для отображений (mappings) используемых в большинстве других языков, объекты класса Class
и рефлексия позволяют более просто описывать рефлексивные (reflective) объекты, рассматривающие другие объекты в качестве данных.
В чисто секвенциальном окружении (sequential environment) на этом можно было бы остановиться. Но, прежде чем продолжить, рассмотрим налагаемые таким отображением на обобщенную объектную модель ограничения. В секвенциальной JVM было бы невозможно напрямую сымитировать множество конкурентно-взаимодействующих объектов waterTank
. И из-за того, что в этом случае весь процесс передачи сообщений выполнялся бы с помощью последовательных процедурных вызовов, не было бы нужды в правилах определяющих может ли несколько сообщений обрабатываться параллельно - этого никогда бы не произошло. Таким образом, секвенциальная ОО обработка ограничивает, допустимые для реализации, виды высокоуровневой архитектуры.
Активные объекты
С другой стороны спектра возможных вариантов отображения находятся модели активных объектов (active object), также известные как модели акторов (actor models), в которых каждый объект обладает автономностью. Каждый из них может быть настолько же мощным как и секвенциальная JVM. Представления внутренних классов и объектов могут принимать такие же формы как и те, что используются в пассивных фреймворках. Например, в рассматриваемом случае каждый waterTank
может быть отображен на отдельный активный объект загрузкой его описания в отдельную JVM, и разрешения ей имитировать определенные действия.
Модель активных объектов представляет собой общее высокоуровневое представление объектов в распределенных ОО-системах: различные объекты могут располагаться на различных машинах, из-за чего расположение и административный домен объекта часто являются важными программными факторами. Передача сообщений реализуется с использованием удаленного механизма связи (remote communication) (например, с помощью сокетов), который может поддерживать любое число протоколов, включая однонаправленный обмен сообщениями (т.е. не требующий обязательного ответа), многоадресную рассылку (одновременная рассылка одного и того же сообщения многим получателям), и обмен запрос-ответ в процедурном стиле.
Эта модель также служит в качестве объектно-ориентированного представления большинства процессов уровня ОС, каждый из которых является максимально независимым и разделяет с другими процессами минимальное количество ресурсов.
Смешанные модели
Модели и отображения, лежащие в основе поддержки параллелизма в Java, находятся между двух крайностей пассивных и активных моделей. Вся JVM может включать нескольких потоков, каждый из которых действует практически таким же образом как и единственная последовательная JVM. Однако, в отличие от активных объектов, все эти потоки могут иметь общий доступ к одному и тому же набору лежащих в основе пассивных представлений (set of underlying passive representations).
Такой стиль отображения позволяет имитировать любой из крайних вариантов. Исключительно пассивные последовательные модели могут быть запрограммированы с использованием только одного потока. Исключительно активные модели могут быть запрограммированы c помощью создания отдельного потока для каждого активного объекта, избегая ситуаций при которых более, чем один поток имеет доступ к данному пассивному представлению, и используя конструкции имеющие такой же смысловой эффект, что и удаленная передача сообщений. Однако, большинство конкурентных программ используют нечто среднее между этими двумя подходами.
Конкурентные ОО-модели основанные на потоках концептуально разделяют нормальные пассивные объекты и активные объекты (потоки). Но пассивные объекты обычно выражают свою осведомленность о параллелизме, отсутствующую в секвенциальном (sequential) программировании, например защищая себя с помощью блокировок. Активные объекты также отличаются тем, что они несколько проще, чем в модели акторов, поддерживая только некоторые операции (такие как run
). Но к проектированию конкурентных ОО-систем можно подойти с любой из сторон - либо сделав пассивные объекты более умными для работы в многопоточном окружении, либо упростив активные объекты с целью их более простого выражения с использованием конструкций потоков.
Одной из причин поддержки такой объектной модели является то, что она прямо и эффективно отображается на обычные однопроцессорные и многопроцессорные с разделяемой памятью (SMP, shared-memory multiprocessor) операционные системы и аппаратное обеспечение: потоки могут быть связаны (bound) с ЦПУ когда это возможно и желательно, иначе используется разделение времени; локальное состояние потока отображается на регистры и ЦПУ; представление разделяемых объектов отображается на разделяемую основную память.
Степень контроля программистом этих отображений является основным отличием конкурентного программирования от различного вида параллельного программирования. Классическое параллельное программирование включает шаги явного проектирования отображения потоков, задач, или процессов, а также данных на физические процессоры и их локальную память. Конкурентное программирование оставляет бОльшую часть этих задач JVM (и ОС). Данное решение увеличивает переносимость ценой необходимости сглаживания различий качества реализации этих отображений.
Разделение времени осуществляется с помощью применения такой же стратегии отображения на сами потоки: представления объектов класса Thread
находятся в памяти, а планировщик управляет переключениями контекста (context switches) при которых состояние ЦПУ, соответствующее одному потоку, сохраняется в ассоциированном с ним представлении памяти, а состояние, соответствующее другому потоку, восстанавливается.
Возможны некоторые уточнения и расширения данной модели отображения. Например, приложения и системы хранящие объекты, обычно, для поддержки их представления вместо основной памяти используют БД.