2015-08-11 2 views
0

Идиома с одной проверкой может использоваться для реализации потокобезопасного инициализации с (по сравнению с двойной проверкой) возможного недостатка траты некоторое время вычисления несколькими параллельными вводами. ЭтоВнедрение параметризованного поточно-безопасного кэша ленивого инициализации с использованием ConcurrentHashMap (без ключевого слова volatile)

Single-проверка идиом

private volatile FieldType field; 
FieldType getField() { 
    FieldType result = field; 
    if (result == null) { 
    field = result = computeFieldValue(); 
    } 
    return result; 
} 

Здесь нам нужно летучий field, чтобы избежать, что частично инициализирован объект передается в другой поток, то есть - назначение (запись) неявно выполняет необходимая синхронизация.

Я хотел бы реализовать параметризованных ленивых инициализации кэша, который, по существу, представленные в Map<Integer, Object>, где каждый элемент создается с помощью ленивой инициализации.

Мой вопрос: достаточно ли использовать ConcurrentHashMap, чтобы избежать проблемы с частичной инициализацией. То есть, в этом случае потокобезопасной реализация кэш отложенной инициализации с помощью одной проверки идиомы могут быть заданы

private final ConcurrentHashMap<Integer, ItemType> items = new ConcurrentHashMap<Integer, ItemType>(); 

ItemType getItem(Integer index) { 
    ItemType result = items.get(index); 
    if (result == null) { 
    result = computeItemValue(index); 
    items.put(index, result); 
    } 
    return result; 
} 

Другими словами: я предполагаю, что «items.put (индекс, результат) 'выполняет необходимую синхронизацию (поскольку это запись). Обратите внимание, что вопрос здесь может быть двояким: сначала мне интересно, работает ли это в (а) текущей реализации JVM, во-вторых (что еще более важно), интересно ли это (с учетом документации/контракта) ConcurrentHashMap.

Примечание: Здесь я предполагаю, что computeItemValue генерирует неизменяемый объект и гарантирует безопасность потока в смысле одноименной идиомы (то есть, когда построение объекта завершено, возвращаемый объект ведет себя одинаково для всех потоков). Я предполагаю, что это статья 71 в книге Дж. Блоха.

+2

Ни одно из ваших предложений является поточно.Несколько потоков могут вызывать 'computeFieldValue' и присваивать значение полю/добавлять его к карте. Для 'ConcurrentHashMap' используйте' putIfAbsent' или 'computeIfAbsent'. –

+0

Они потокобезопасны в смысле одноименной идиомы, что computeFieldValue создает неизменяемый объект, который ведет себя одинаково, даже если он построен несколько раз ... (пожалуйста, единичная идиома в книге Блоха). –

+0

@ChristianFries Правильный одноэлементный шаблон не инициализирует экземпляр более одного раза. – Kayaman

ответ

1

достаточно, чтобы использовать ConcurrentHashMap, чтобы избежать проблемы с частичной инициализацией.

Да, есть явное случается - перед заказом для объектов, считанных из CHM, относительно их записи.

Итак, вы покрываете видимость, но не коснулись атомарности. Есть два компонента на атомарность

  • запоминанием
  • пут, если еще не

Вы не достигают либо. То есть, для memoization вы можете создать несколько объектов (не на самом деле lazy-init). И для put, если нет, CHM может принимать более одного значения, так как вы не вызываете putIfAbsent.

Основываясь на вашем последнем обновлении, если они будут одним и тем же объектом и являются неизменяемыми, вы должны быть в порядке, несмотря на дополнительную конструкцию.

+0

Спасибо. Что, если у нас есть ситуация, что у нас есть два разных неизменяемых объекта, которые ведут себя неразличимыми (за исключением того, что у них могут быть два разных адреса/id)? (Я отредактирую свой вопрос в этих направлениях) –

+0

@ChristianFries Что вы подразумеваете под идентификатором? Как последовательность? В этом случае у вас проблемы. Если вы можете создать два объекта «ItemType a = computeItemValue (index)» и «ItemType b = computeItemValue (index)» с тем же «индексом», что 'a.equals (b)' false. Это не сработает. –

+0

Если вы используете Java 8 и можете использовать 'computeIfAbsent', как предложил Бретт Оккен, чем определенно, определенно сделайте это. –

2

В Java 8, вы можете использовать computeIfAbsent, чтобы избежать даже возможности дублирования инициализации:

private final ConcurrentHashMap<Integer, ItemType> items = new ConcurrentHashMap<Integer, ItemType>(); 

ItemType getItem(Integer index) { 
    return items.computeIfAbsent(index, this::computeItemValue); 
} 
Смежные вопросы