Является ли потоком таблицы Guava безопасным, когда его резервные карты являются потокобезопасными?

Будет ли Guava метод tables.newCustomTable(Map, Supplier) возвращает потокобезопасные таблицы при поставке с помощью безопасных по потоку карт? Например:

public static <R, C, V> Table<R, C, V> newConcurrentTable() {
  return Tables.newCustomTable(
      new ConcurrentHashMap<R, Map<C, V>>(),
      new Supplier<Map<C, V>>() {
        public Map<C, V> get() {
          return new ConcurrentHashMap<C, V>();
        }
      });
}

Действительно ли этот код возвращает параллельные таблицы?

Ответы

Ответ 1

Из документа: "Если несколько потоков обращаются к этой таблице одновременно, и один из потоков изменяет таблицу, ее необходимо синхронизировать извне".

Совпадающих коллекций сборок недостаточно.

Ответ 2

Кевин Бурриллион прав. Техническая причина для карты, которую вы создали, чтобы не быть потокобезопасной, заключается в том, что даже если используемые вами карты являются потокобезопасными, операции с таблицей могут быть неактивными. Позвольте мне привести пример put, реализованный в StandardTable, который используется Tables.newCustomTable:

public V put(R rowKey, C columnKey, V value) {
  Map<C, V> map = backingMap.get(rowKey);
  if (map == null) {
    map = factory.get();
    backingMap.put(rowKey, map);
  }
  return map.put(columnKey, value);
}

Безопасность при обращении с корпусом map == null скомпрометирована. А именно, два или более потока могут войти в этот блок и создать новую запись для columnKey, а последняя для выполнения backingMap.put(rowKey, map) в конечном итоге переопределит запись для columnKey в backingMap, что приведет к потеря операций put, выполняемых другими потоками. В частности, результат этой операции в многопоточной среде не является детерминированным, что эквивалентно утверждению, что эта операция не является потокобезопасной.

Правильная реализация этого метода:

public V put(R rowKey, C columnKey, V value) {
    ConcurrentMap<C, V> map = table.get(rowKey);
    if (map == null) {
        backingMap.putIfAbsent(rowKey, factory.get());
    }
    map = backingMap.get(rowKey);
    return map.put(columnKey, value);
}

В настоящее время я изучаю, можно ли использовать реализацию ForwardingTable вместе с тем, что вы хотели сделать, чтобы получить корректный поток ConcurrentTable.

Но, честно говоря, я думаю, что причина отсутствия поточной реализации Table заключается в том, что сам интерфейс не предоставляет никаких конструкций concurrency, таких как putIfAbsent или replace.