HotLog

суббота, 28 августа 2010 г.

Java Memory Model

Данная статья является пересказом-переводом JSR-133 FAQ

Что такое Memory Model?

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

Почему все так сложно?

Что бы дать максимальную свободу для проведения различных оптимизаций.

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

Если с точки зрения потока, выполняющего метод test ничего не изменилось, то другой поток может заметить, что x стало равным 4 раньше, чем переменной b было присвоено значение 3. Для устранения подобных двусмысленностей введены правила очередности, которые описывают, какие из действий в прошлом окажутся видимыми для данного потока в настоящем.

Правила очередности.

Пусть в момент времени T1 было произведено действие Д1, а в некоторый момент времени T2>T1 произведено действие Д2. Если Д1 было, например, запись в переменную, а Д2 - чтение из этой же переменной, то в общем случае не гарантируется, что Д2 прочитает новое значение. То есть не гарантируется, что выполнено отношение "Д2 после Д1". Следующие правила определяют, в каких конкретных случаях такое отношение гарантируется.

  1. В рамках одного потока: Действие Д1 происходит перед действием Д2, если в программе действие Д2 стоит позже, чем Д1. Это означает, что инструкции могут переставляться только так, что бы из программы это определить было невозможно (речь идет только об одном потоке).Пример

    Порядок присвоения значений переменным a и b может быть произвольным, но гарантируется, что при вычислении значения для переменной x все предыдущие значения будут видны (и x будет равен 5).

  2. Освобождение монитора происходит всегда раньше, чем последующий захват того же самого монитора. Это означает следующее: Пусть поток П1 произвел изменения И1, захватил монитор M, произвел изменения И2 и освободил монитор М. Пусть после этого поток П2 захватил тот же самый монитор M. Тогда потоку П2 будут видны все изменения, которые произвел поток П1: как И1 так и И2.

  3. Запись в volatile-переменную всегда происходит раньше, чем последующее чтение из нее. То есть все так же, как и в предыдущем пункте с заменой захвата монитора на чтение volatile-переменной и освобождения монитора - на запись той же самой переменной. Таким образом можно синхронизировать значения переменных, изменяемых из разных потоков без захвата монитора и связанной с этим блокировкой (см. ConcurrentHashMap, например).

  4. Вызов метода start() у потока происходит всегда до того, как произойдут любые действия в новом потоке. Это означает, что на момент старта потока он синхронизирован с тем потоком, которые его стартовал.

  5. Все инструкции в потоке гарантированно происходят до того, как метод join() на этом потоке успешно возвратится. Это означает, что если поток П1 ожидает поток П2 методом П2.join() и join() успешно завершается, то все изменения, произведенные П2, доступны из П1.

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

    Здесь ссылка на класс утекает через публикацию ее в статик-поле instance. В промежутке между (1) и (2) до поля instance.a может дотянуться другой поток и увидеть, что а==0, а еще через некоторое время увидеть, что final поле a изменило свое значение на a==5. Что бы этого не произошло, никогда не публикуйте ссылку на еще не созданный объект.

  7. Если final-поле является ссылкой на объект O1, то так же гарантируется, что после нормального завершения конструктора, все поля объекта O1 так же будут видны всем потокам. Таким образом, если final-поле является ссылкой, например, на массив, не нужно беспокоиться, что другие потоки увидят правильно значение ссылки, но неправильно содержимое массива ("правильное" здесь означает "правильное на момент завершения конструктора", а не "последнее присвоенное").

Комментариев нет:

Отправить комментарий