2012-04-20 1 views
10

Я хочу, чтобы я правильно понял поведение «Эффективно неизменяемых объектов» в соответствии с моделью памяти Java.Эффективно неизмеримый объект

Допустим, у нас есть изменяемый класс, который мы хотим опубликовать как эффективно неизменны:

class Outworld { 
    // This MAY be accessed by multiple threads 
    public static volatile MutableLong published; 
} 

// This class is mutable 
class MutableLong { 
    private long value; 

    public MutableLong(long value) { 
    this.value = value; 
    } 

    public void increment() { 
    value++; 
    } 

    public long get() { 
    return value; 
    } 
} 

Мы делаем следующее:

// Create a mutable object and modify it 
MutableLong val = new MutableLong(1); 
val.increment(); 
val.increment(); 
// No more modifications 
// UPDATED: Let's say for this example we are completely sure 
//   that no one will ever call increment() since now 

// Publish it safely and consider Effectively Immutable 
Outworld.published = val; 

Вопрос заключается в: Является ли модель памяти Java гарантией того, что все потоки ДОЛЖНЫ иметь Outworld.published.get() == 3?

Согласно Java Concurrency In Practice это должно быть правдой, но, пожалуйста, исправьте меня, если я ошибаюсь.

3.5.3. Safe Publication Idioms

Чтобы опубликовать объект безопасно, как ссылка на объект, так и состояние объекта должны быть видимыми для других потоков одновременно. Правильно построенный объект можно безопасно опубликовать:
- Инициализация ссылки на объект из статического инициализатора;
- Сохранение ссылки на него в поле volatile или AtomicReference;
- Сохранение ссылки на него в конечном поле правильно построенного объекта; или
- Сохранение ссылки на него в поле, которое должным образом защищено блокировкой.

3.5.4. Эффективно Неизменяемые объекты

Безопасно опубликованные эффективно неизменяемые объекты могут быть безопасно использованы любой нитью без дополнительной синхронизации.

+0

Просьба показать [* статический инициализатор *] (http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html), который устанавливает состояние объекта до того, как ссылка на него сделана видимой , – trashgod

ответ

5

Да. Операции записи на MutableLong сопровождаются отношением happens-before (от изменчивого) до считывания.

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

+1

Ваш комментарий (2-й абзац) кажется несовместимым с вашим ответом (1-й абзац), и это происходит раньше, чем связано с изменчивым чтением. Можете ли вы уточнить? – assylias

+2

Предположим, что поток T1 safe опубликован в T2. Между T1 и T2 существует * происходит-до *. Но если тот же самый объект неявно опубликован от T2 до T3, то нет * происходит-до * от T2 до T3, и поэтому также не происходит * до * от T1 до T3. –

2

вопрос: Является ли модель памяти Java гарантией того, что все потоки ДОЛЖНЫ иметь Outworld.published.get() == 3?

Короткий ответ no. Поскольку другие потоки могут получить доступ к Outworld.published, прежде чем он будет прочитан.

После того, как Outworld.published = val; был выполнен, при условии, что никакие другие модификации, сделанные с помощью val, - да - всегда 3.

Но если какая-либо нить выполняет val.increment, тогда ее значение может отличаться для других потоков.

+0

Да, я говорю о ситуации, когда мы можем считать этот объект Эффективно неизменным; то есть мы абсолютно уверены, что ни один другой поток никогда не назовет 'increment()' на нем. Я обновил пример, чтобы быть более конкретным. –

4

Существует несколько условий, которые должны быть выполнены для модели памяти Java, чтобы гарантировать, что Outworld.published.get() == 3:

  • сниппет кода, который размещен, который создает и увеличивает MutableLong, затем устанавливает Outworld.published поле, должен произойти с видимость между ступенями. Одним из путей достижения этого тривиально является то, что весь этот код работает в одном потоке - гарантируя «семантику as-if-serial». Я предполагаю, что это то, что вы намеревались, но подумал, что стоит отметить.
  • читает Outworld.published должно быть происходит после семантика задания. Примером этого может быть тот же самый запуск потока Outworld.published = val;, затем запустите другие потоки, которые могли бы прочитать значение. Это гарантировало бы «, как если бы серийный» семантика, предотвращая повторный порядок чтения перед назначением.

Если вы в состоянии предоставить эти гарантии, JMM гарантирует, что все потоки будут видны Outworld.published.get() == 3.


Однако, если вас интересует общий совет по проектированию программ в этой области, читайте дальше.

Для гарантии того, что никакого другого потока никогда увидеть различные значения для Outworld.published.get(), вы (разработчик) должны гарантировать, что ваша программа не изменяет значение в любом случае. Либо путем последующего выполнения Outworld.published = differentVal;, либо Outworld.published.increment();. Несмотря на то, что можно гарантировать, что может быть намного проще, если вы разрабатываете ваш код, чтобы избежать как изменяемого объекта, так и с использованием статического неконечного поля в качестве глобальной точки доступа для нескольких потоков:

  • вместо публикации MutableLong, скопируйте соответствующие значения в новый экземпляр другого класса, состояние которого не может быть изменено. Например: введите класс ImmutableLong, который присваивает value полюсу final и не имеет метода increment().
  • вместо нескольких потоков, обращающихся к статическому нефинальному полю, передать объект в качестве параметра в свои Callable/Runnable реализации. Это предотвратит возможность того, что один поток изгоев переназначает значение и мешает другим, и легче рассуждать, чем статическое полевое переназначение. (По общему признанию, если вы имеете дело с устаревшим кодом, это проще сказать, чем сделать).
+0

Я полностью согласен с тем, что вы говорите. Синхронизация слишком проста, чтобы ошибиться. Я бы избегал этого любой ценой. Совместная изменчивость - это просто не безопасный способ разработки алгоритма или программы. Я думаю, каждый программист должен посвятить некоторое время изучению концепций функционального программирования и применить их к миру оо. Это определенно стоит того. – bennidi