Язык программирования Java и виртуальная машина Java (JVM) были разработаны для поддержки параллельного программирования , и все выполнение происходит в контексте потоков . Доступ к объектам и ресурсам может осуществляться многими отдельными потоками; каждый поток имеет свой собственный путь выполнения, но потенциально может получить доступ к любому объекту в программе. Программист должен гарантировать, что доступ для чтения и записи к объектам должным образом скоординирован (или " синхронизирован") между потоками. Синхронизация потоков гарантирует, что объекты изменяются только одним потоком за раз и что потоки не имеют доступа к частично обновленным объектам во время модификации другим потоком. Язык Java имеет встроенные конструкции для поддержки этой координации.
Процессы и потоки
Большинство реализаций виртуальной машины Java работают как единый процесс, а в языке программирования Java параллельное программирование в основном связано с потоками (также называемыми облегченными процессами ). Несколько процессов могут быть реализованы только с несколькими JVM.
Объекты потока
Потоки совместно используют ресурсы процесса, включая память и открытые файлы. Это делает общение эффективным, но потенциально проблематичным. В каждом приложении есть хотя бы один поток, называемый основным потоком. Основной поток имеет возможность создавать дополнительные потоки как объекты Runnable
или Callable
. ( Callable
Интерфейс похож на тот Runnable
, что оба предназначены для классов, экземпляры которых потенциально выполняются другим потоком. Однако A Runnable
не возвращает результат и не может генерировать проверенное исключение.)
Каждый поток можно запланировать на отдельном ядре ЦП или использовать квантование времени на одном аппаратном процессоре или квантование времени на многих аппаратных процессорах. Универсального решения того, как потоки Java сопоставляются с потоками собственной ОС, не существует. Каждая реализация JVM может делать это по-своему.
Каждый поток связан с экземпляром класса Thread. Потоками можно управлять либо напрямую с помощью объектов Thread, либо с помощью абстрактных механизмов, таких как Executor
s и java.util.concurrent
коллекции.
Запуск обсуждения
Два способа начать обсуждение:
Предоставьте работающий объект
открытый класс HelloRunnable реализует Runnable { @Override public void run () { System . из . println ( "Привет из треда!" ); } Общественности статического недействительный основной ( Строка [] арг ) { ( новая тема ( новая HelloRunnable ())). start (); } }
Поток подкласса
открытый класс HelloThread расширяет Thread { @Override public void run () { System . из . println ( "Привет из треда!" ); } public static void main ( String [] args ) { ( new HelloThread ()). start (); } }
Прерывания
Прерывание - это указание потоку, что он должен остановить то, что он делает, и сделать что-то еще. Поток отправляет прерывание, вызывая прерывание для объекта Thread, чтобы поток был прерван. Механизм прерывания реализован с использованием внутреннего флага, известного как состояние прерывания. Вызов Thread.interrupt
устанавливает этот флаг. По соглашению, любой метод, который завершается с помощью InterruptedException
сброса состояния прерывания, когда это происходит. Однако всегда возможно, что статус прерывания будет немедленно снова установлен другим потоком, вызывающим прерывание.
Присоединяется
Эти Thread.join
методы позволяют одному потоку ждать завершения другого.
Исключения
Неперехваченные исключения, вызванные кодом, завершат поток. Поток main
выводит исключения в консоль, но для созданных пользователем потоков требуется зарегистрированный обработчик. [1] [2]
Модель памяти
Модель памяти Java описывает, как потоки в языке программирования Java взаимодействуют через память. На современных платформах код часто выполняется не в том порядке, в котором он был написан. Он переупорядочивается компилятором , процессором и подсистемой памяти для достижения максимальной производительности. Язык программирования Java не гарантирует линеаризуемость , или даже последовательной согласованности , при чтении или записи полей общих объектов, и это должно позволить оптимизации компилятора (например, выделение регистров , общее устранение подвыражения и устранение избыточных чтения ) все из которых работают путем переупорядочивания чтения-записи в памяти. [3]
Синхронизация
Потоки общаются в основном путем совместного использования доступа к полям и объектам, на которые ссылаются поля. Эта форма связи чрезвычайно эффективна, но делает возможными два типа ошибок: интерференцию потоков и ошибки согласованности памяти. Инструмент, необходимый для предотвращения этих ошибок, - это синхронизация.
Переупорядочение может вступать в игру в неправильно синхронизированных многопоточных программах, где один поток может наблюдать за эффектами других потоков и может обнаруживать, что доступ к переменным становится видимым для других потоков в другом порядке, чем при выполнении или указанном в программе. В большинстве случаев одному потоку все равно, что делает другой. Но когда это происходит, для этого и нужна синхронизация.
Для синхронизации потоков Java использует мониторы , которые представляют собой высокоуровневый механизм, позволяющий только одному потоку одновременно выполнять область кода, защищенную монитором. Поведение мониторов объясняется с точки зрения замков ; с каждым объектом связана блокировка.
Синхронизация имеет несколько аспектов. Наиболее понятным является взаимное исключение - только один поток может одновременно удерживать монитор, поэтому синхронизация на мониторе означает, что как только один поток входит в синхронизированный блок, защищенный монитором, никакой другой поток не может войти в блок, защищенный этим монитором, пока первый поток выходит из синхронизированного блока.
Но синхронизация - это не только взаимное исключение. Синхронизация гарантирует, что записи в память, выполняемые потоком до или во время синхронизированного блока, становятся видимыми предсказуемым образом для других потоков, которые синхронизируются на том же мониторе. После выхода из синхронизированного блока мы освобождаем монитор, что приводит к сбросу кеша в основную память, так что записи, сделанные этим потоком, могут быть видны другим потокам. Прежде чем мы сможем ввести синхронизированный блок, мы приобретаем монитор, который делает недействительным кеш локального процессора, так что переменные будут перезагружены из основной памяти. После этого мы сможем увидеть все записи, сделанные в предыдущем выпуске.
Чтения - записи в поля являются линеаризуемыми, если либо поле является изменчивым , либо поле защищено уникальной блокировкой, которую получают все считывающие и записывающие.
Замки и синхронизированные блоки
Поток может добиться взаимного исключения либо путем ввода синхронизированного блока или метода, который получает неявную блокировку, либо путем получения явной блокировки (например, ReentrantLock из пакета java.util.concurrent.locks). Оба подхода имеют одинаковые последствия для поведения памяти. Если все обращения к определенному полю защищены одной и той же блокировкой, то операции чтения-записи в это поле являются линеаризуемыми (атомарными).
Неустойчивые поля
Применительно к полю Java volatile
гарантирует, что:
- (Во всех версиях Java) Существует глобальный порядок чтения и записи в изменчивую переменную. Это означает, что каждый поток, обращающийся к изменчивому полю, будет читать свое текущее значение перед продолжением вместо (потенциально) использования кэшированного значения. (Однако нет никакой гарантии относительно относительного упорядочения изменчивых операций чтения и записи с обычными операциями чтения и записи, что означает, что это обычно бесполезная конструкция потоковой передачи.)
- (В Java 5 или более поздней версии ) Чтения и записи Volatile устанавливают связь « происходит раньше» , что очень похоже на получение и освобождение мьютекса. [4] Это отношение является просто гарантией того, что записи в память одним конкретным оператором видны другому конкретному оператору.
Неустойчивые поля линеаризуемы. Чтение изменчивого поля похоже на получение блокировки: рабочая память становится недействительной, и текущее значение изменчивого поля повторно считывается из памяти. Запись изменчивого поля похожа на снятие блокировки: изменчивое поле немедленно записывается обратно в память.
Заключительные поля
Поле, объявленное как окончательное, не может быть изменено после его инициализации. Конечные поля объекта инициализируются в его конструкторе. Если конструктор следует определенным простым правилам, то правильное значение любых полей final будет видно другим потокам без синхронизации. Правило простое: this
ссылка не должна выходить из конструктора до его возврата.
История
Начиная с JDK 1.2 , Java включает стандартный набор классов коллекций, структуру коллекций Java.
Дуг Ли , который также участвовал в реализации инфраструктуры коллекций Java, разработал пакет параллелизма , включающий несколько примитивов параллелизма и большую батарею классов, связанных с коллекциями. [5] Эта работа была продолжена и обновлена как часть JSR 166, который возглавлял Дуг Ли.
JDK 5.0 включает множество дополнений и уточнений к модели параллелизма Java. API-интерфейсы параллелизма, разработанные JSR 166, также впервые были включены как часть JDK. JSR 133 обеспечивает поддержку четко определенных атомарных операций в многопоточной / многопроцессорной среде.
В выпусках Java SE 6 и Java SE 7 представлены обновленные версии API-интерфейсов JSR 166, а также несколько новых дополнительных API-интерфейсов.
Смотрите также
Заметки
- ^ Оракул. «Интерфейс Thread.UncaughtExceptionHandler» . Проверено 10 мая 2014 .
- ^ «Тихая смерть потока от необработанных исключений» . literatejava.com . Проверено 10 мая 2014 .
- ^ Herlihy, Морис, и Нир Шавит. «Искусство многопроцессорного программирования». PODC. Vol. 6. 2006.
- ^ Раздел 17.4.4: Порядок синхронизации «Спецификация языка Java®, Java SE 7 Edition» . Корпорация Oracle . 2013 . Проверено 12 мая 2013 .
- ^ Дуг Ли . «Обзор пакета util.concurrent Release 1.3.4» . Проверено 1 января 2011 .
Примечание. После выпуска J2SE 5.0 этот пакет переходит в режим обслуживания: будут выпущены только существенные исправления. Пакет J2SE5 java.util.concurrent включает улучшенные, более эффективные, стандартизированные версии основных компонентов в этом пакете.
Рекомендации
- Гетц, Брайан; Джошуа Блох; Джозеф Баубир; Дуг Ли; Дэвид Холмс; Тим Пайерлс (2006). Параллелизм Java на практике . Эддисон Уэсли. ISBN 0-321-34960-1.
- Ли, Дуг (1999). Параллельное программирование на Java: принципы и шаблоны проектирования . Эддисон Уэсли. ISBN 0-201-31009-0.
Внешние ссылки
- Учебник Oracle по параллелизму в Java
- Страница модели памяти Java Уильяма Пью
- Учебное пособие по параллелизму Java от Якоба Дженкова
- Анимация параллелизма Java от Виктора Граци
- Средство проверки безопасности потоков для классов Java