Interested Article - Модель памяти Java
- 2020-07-07
- 1
Модель памяти Java ( англ. Java Memory Model , JMM ) описывает поведение потоков в среде исполнения Java . Модель памяти — часть семантики языка Java, и описывает, на что может и на что не должен рассчитывать программист , разрабатывающий ПО не для конкретной Java-машины, а для Java в целом.
Исходная модель памяти Java (к которой, в частности, относится «потоколокальная память»), разработанная в 1995 году, считается неудачной: многие оптимизации невозможно провести, не потеряв гарантию безопасности кода. В частности, есть несколько вариантов написать многопоточного « одиночку »:
- либо каждый акт доступа к одиночке (даже когда объект давно создан, и ничего уже не может измениться) будет вызывать межпоточную блокировку ;
- либо при определённом стечении обстоятельств система выдаст недостроенного одиночку;
- либо при определённом стечении обстоятельств система создаст два одиночки;
- либо конструкция будет зависеть от особенностей поведения той или иной машины.
В J2SE 5.0 (30 сентября 2004) появилась новая модель памяти, разработанная через Java Community Process под названием JSR-133 . Она лучше отражала принципы работы современных процессоров и компиляторов, и другие языки брали идеи из модели Java. Основной вклад в её создание внесли Сарита Адве , Джереми Мейсон и .
Предпосылки
Язык программирования Java позволяет писать многопоточные программы. Поскольку Java может работать на самых разных процессорах и ОС, синхронизация потоков особенно затрудняется. Чтобы программист мог сделать какие-то выводы о поведении программ, разработчики Java решили чётко определить различные варианты поведения всех программ на Java.
На современных компьютерах код ради скорости выполняется не в том порядке, в котором написан. Перестановка выполняется компилятором, процессором и подсистемой памяти. На многопроцессорных машинах каждое ядро может иметь свой кэш , не синхронный с основной памятью. А значит, у разных процессоров могут быть одновременно разные значения одной и той же переменной. Когда потоки много взаимодействуют друг с другом, это обычно нежелательно: чтобы быть в курсе сделанного другим процессором, нужно много времени.
К тому же в однопоточной среде достаточно потребовать от системы «псевдопоследовательного» выполнения программы — наблюдателю, который видит только
ввод-вывод
, будет казаться, что все действия выполняются в том порядке, в котором они появились в программе, даже если это не так. Однако любому, кто сможет «заглянуть» в память компьютера — в том числе другому потоку — все эти «трюки» будут заметны. Рассмотрим два потока, которые одновременно выполняют такой код (
x
и
y
изначально нули).
Поток 1 | Поток 2 |
---|---|
x = 1; | int r1 = y; |
y = 2; | int r2 = x; |
Если нет перестановок, а поток 2 считал
y=2
, гарантированно должно быть
x=1
: ведь запись в
x
выполняется прежде, чем запись в
y
. С перестановкой оказывается возможна и, казалось бы, парадоксальная ситуация:
r1=2
,
r2=0
.
Такое поведение многопоточных программ модель JMM разрешает, но описывает, когда такие перестановки возможны. Таким образом, модель памяти Java накладывает ограничения на взаимодействие потоков, чтобы не потерять возможные оптимизации и в то же время дать возможность многопоточным программам вести себя надёжно и предсказуемо там, где это нужно. Программист может делать какие-либо заключения о том, в каком порядке выполняется код на многопоточной машине, даже несмотря на оптимизации, проводимые компилятором, процессором и кэшем.
Модель памяти
Правило № 1: однопоточные программы исполняются псевдопоследовательно. Это значит: в реальности процессор может выполнять несколько операций за такт, заодно изменив их порядок, однако все зависимости по данным остаются, так что поведение не отличается от последовательного.
Правило № 2: нет невесть откуда взявшихся значений. Чтение любой переменной (кроме не-
volatile
long
и
double
, для которых это правило может не выполняться) выдаст либо значение по умолчанию (ноль), либо что-то, записанное туда другой командой.
И правило № 3: остальные события выполняются по порядку, если связаны отношением строгого частичного порядка «выполняется прежде» ( англ. happens before ).
«Выполняется прежде»
«Выполняется прежде» (
англ.
happens before
) — отношение
строгого частичного порядка
(антирефлексивное, антисимметричное, транзитивное), введённое между атомарными командами (
++
и
--
не атомарны), придуманное
Лесли Лэмпортом
и не означающее «физически прежде». Оно значит, что вторая команда будет «в курсе» изменений, проведённых первой.
В частности, одно выполняется прежде другого для таких операций (список не исчерпывающий):
-
Синхронизация и
мониторы
:
-
Захват монитора (начало
synchronized
, методlock
) и всё, что после него в том же потоке. -
Возврат монитора (конец
synchronized
, методunlock
) и всё, что перед ним в том же потоке.- Таким образом, оптимизатор может заносить строки в синхроблок, но не наружу.
- Возврат монитора и последующий захват другим потоком.
-
Захват монитора (начало
-
Запись и чтение:
- Любые зависимости по данным (то есть запись в любую переменную и последующее чтение её же) в одном потоке.
-
Всё, что в том же потоке перед записью в
volatile
-переменную, и сама запись. -
volatile
-чтение и всё, что после него в том же потоке. -
Запись в
volatile
-переменную и последующее считывание её же . Таким образом,volatile
-запись делает с памятью то же, что возврат монитора, а чтение — то же, что захват . А значит: если один поток записал вvolatile
-переменную, а второй обнаружил это, всё, что предшествует записи, выполняется раньше всего, что идёт после чтения; см. иллюстрацию.-
Для объектных переменных (например,
volatile List x;
) столь сильные гарантии выполняются для ссылки на объект, но не для его содержимого.
-
Для объектных переменных (например,
-
Обслуживание объекта:
- Статическая инициализация и любые действия с любыми экземплярами объектов.
-
Запись в
final
-поля в конструкторе и всё, что после конструктора. Как исключение из всеобщей транзитивности, это соотношение happens-before не соединяется транзитивно с другими правилами и поэтому может вызвать межпоточную гонку . -
Любая работа с объектом и
finalize()
.
-
Обслуживание потока:
- Запуск потока и любой код в потоке.
- Зануление переменных, относящихся к потоку, и любой код в потоке.
-
Код в потоке и
join()
; код в потоке иisAlive() == false
. -
interrupt()
потока и обнаружение факта останова.
Влияние
Из-за повсеместного внедрения многопоточных и параллельных систем потребовался инструментарий с чёткой семантикой. Модель памяти Java стала первой попыткой разработать исчерпывающую модель межпоточного взаимодействия для крупного языка программирования .
В
C++03
единственное замечание о многопоточности — для
volatile
-переменных не проводить никаких оптимизаций, связанных с ускорением доступа. Этого тоже не хватало, чтобы задействовать всю мощь переставляющего компилятора/процессора и не получить ошибку, связанную с внеочередным выполнением какой-то команды. Сходная модель памяти вошла в
C++11
.
См. также
Примечания
- . Дата обращения: 21 июля 2013. 6 марта 2012 года.
- ↑ Goetz, Brian (24 февраля 2004). Дата обращения: 18 октября 2010. Архивировано из 8 января 2007 года.
- Jeremy Manson and Brian Goetz. (февраль 2004). — « The Java Memory Model describes what behaviors are legal in multithreaded code, and how threads may interact through memory. It describes the relationship between variables in a program and the low-level details of storing and retrieving them to and from memory or registers in a real computer system. It does this in a way that can be implemented correctly using a wide variety of hardware and a wide variety of compiler optimizations. ». Дата обращения: 18 октября 2010. 4 сентября 2013 года.
- James Gosling, Bill Joy, Guy L. Jr. Steele, Gilad Bracha, Alex Buckley. The Java Language Specification, Java SE 7 Edition. — Pearson Education, 2013. — ISBN 978-0-13-326032-8 .
- . Дата обращения: 21 июля 2013. 11 мая 2013 года.
- . Дата обращения: 18 июля 2013. Архивировано из 25 сентября 2013 года.
-
Ниоткуда, кроме как из конструктора, в
final
-поля писать нельзя. - . Дата обращения: 22 июля 2013. 16 января 2015 года.
- Goetz, Brian (24 февраля 2004). Дата обращения: 17 февраля 2008. Архивировано из 13 августа 2009 года.
- Boehm, Hans . Дата обращения: 17 февраля 2008. Архивировано из 4 сентября 2013 года.
Ссылки
- 13 августа 2009 года. — An article describing problems with the original Java memory model.
- 8 января 2007 года. — Explains the changes JSR 133 made to the Java memory model.
- (англ.)
- (англ.)
- (рус.)
- 2020-07-07
- 1