2017-01-13 7 views
0

Следуя по этому вопросу (Java thread safety - multiple atomic operations?), я не хочу, чтобы добавить больше вопросов к нему, но теперь у меня есть это сомнение:Java CHM синхронизации

private final Map<String, Set<String>> data = Maps.newConcurrentMap(); 

... then in a method ... 

if (data.containsKey("A")) { 
    data.get("A").add("B"); 
} 

Это должно быть что-то вроде этого:

synchronized(data) { 
    if (data.containsKey("A")) { 
     data.get("A").add("B"); 
    } 
} 

Для того, чтобы быть потокобезопасным. Это верно?

Так что операции являются атомарными, но для их объединения потребуется синхронизация, верно? В этот момент было бы целесообразно просто использовать простой HashMap вместо параллельного, поскольку мы вручную обрабатываем синхронизацию?

Есть ли какой-либо метод в CHM, чтобы сделать эту работу атомарно?

+0

Одним из преимуществ над простым HashMap является то, что CHM по-прежнему не требует синхронизации для считывателей. – Thilo

+0

Просьба также показать код, который изменяет CHM. Вышеприведенный код доступен только для чтения (только вызывает 'get' и' containsKey') в отношении карты. – Thilo

+0

@Thilo просто представьте, что метод 'put' используется несколькими потоками. Во всяком случае, меня интересует, нужно ли мне «синхронизироваться», чтобы сделать его потокобезопасным. Кроме того, если есть какая-либо конструкция в CHM, чтобы помочь мне избежать этой синхронизации и сделать «map.addValueIfKeyPresent» (я знаю, что этого метода не существует) – Will

ответ

2

В вашем конкретном случае, вы можете захотеть использовать computeIfPresent method из ConcurrentHashMap:

data.computeIfPresent("A", (k, v) -> { v.add("B"); return v; }); 

Из Javadocs:

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

Так что нет необходимости в явной синхронизации.

+0

Это здорово, поэтому в CHM есть метод для этого ... Отлично! Тем не менее у меня вопрос: если этот метод не существует, будет ли мой первый код в порядке? Или мне нужен второй с явной синхронизацией? – Will

+0

Обычно 'computeIfAbsent()' - это то, что вы хотите. Он инициализирует значение, если оно отсутствует, а затем возвращает его. Но не совсем понятно, что вам нужно здесь. – shmosel

+0

@ По-моему, вам нужно будет синхронизировать явно, но лучший подход был бы предложенным @shmosel: 'data.computeIfAbsent (« A », k -> Sets.newConcurrentHashSet()). Add (« B »)); ' –

1
synchronised(data) { 
    if (data.containsKey("A")) { 
     data.get("A").add("B"); 
    } 
} 

Возможно, вам потребуется больше кода.

Если вы хотите узнать только об этом, то единственная возможная проблема заключается в том, что кто-то удаляет набор, найденный по адресу "A" после вашего if чек. Если вы никогда не удаляете записи на карте, вам совсем не нужна синхронизация.

Если вы одновременно удаляете записи на карте, вы можете использовать computeIfPresent, чтобы получить обновленную карту.

Вы также мог бы сделать

Set<String> set = data.get("A"); 
if (set != null) set.add("B"); 

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

Обратите внимание, что вам необходимо также сделать все эти наборы потокобезопасными.

+1

'containsKey()' и 'get()' все еще являются избыточными операциями. Я бы сделал 'Set set = data.get (" A "); if (set! = null) set.add ("B"); '... Beat me to it :) – shmosel

+0

@shmosel: Я тоже. Кажется, лучше, чем 'computeIfPresent' (потому что новое значение не вычисляется, но существующее значение мутируется). – Thilo