2016-08-15 1 views
1

У меня есть следующий класс:AtomicLongMap чтения потокобезопасность

public class Storage { 
    protected static final AtomicLongMap<String> MAP; 
    protected Storage() { 
       MAP= AtomicLongMap.create(); 
    } 

    public void decrement(String id) { 
     long num = MAP.get(id); 
     if (num != 0) { 
      MAP.decrementAndGet(id); 
     } 
    } 

    public void putIntoActiveAgents(String id, Integer num) { 
     MAP.put(id, num); 
    } 

    public void remove(String id) { 
     MAP.remove(id); 
    } 

    public Long get(String id) { 
     return MAP.get(ID); 
    } 
} 

В моем случае я скажем 6 темы, которые исполнительское подобные вещи: Каждый из них проверяет нить, если долго на карте равен 1, если нет, они называют декремент, если да, они вызывают удаление. Всюду я читал, что AtomicLongMap является потокобезопасным. Я уверен, что это когда кто-то увеличивает или уменьшает длинные числа, но я не уверен, что он по-прежнему является потокобезопасным, когда другой поток считывает значения с этой карты. Мой сценарий: 1. Thread A считывает значение с карты - это 2 (поэтому он уменьшает значение) 2. Thread B считывает значение до того, как счетчик был уменьшен - он все равно возвращает 2, поэтому он также уменьшает значение. 3. В результате никто не видит, что значение установлено равным 1.

Мой вопрос в таком случае, мне нужно сделать MAP синхронизированным?

+1

Вы не производите синхронизацию поля или объекта. Вы выполняете синхронизацию методов или блоков кода. Ваш метод decment() не является потокобезопасным (т. Е. Не атомарным), и ваш сценарий, когда потоки проверяют значение, а затем либо уменьшают, либо удаляют, также не являются потокобезопасными. Сначала вы должны инкапсулировать это в метод. А затем сделайте этот метод потокобезопасным. –

+0

Вы ищете 'ConcurrentHashMultiset'? –

ответ

1

Если вы используете Java 8, глядя на свой код, я предлагаю вам использовать ConcurrentHashMap. Интерфейс карты в Java 8 обновлен новыми функциями, такими как computeIfPresent(). Таким образом, ваша функция «декремент (String id)» будет выглядеть так:

public class Storage { 
    protected static final Map<String, Long> MAP = new ConcurrentHashMap<>(); 

    public void decrement(String id) { 
     MAP.computeIfPresent(id, (id, currentValue) -> --currentValue); 
    } 

    public void putIntoActiveAgents(String id, Integer num) { 
     MAP.put(id, num); 
    } 

    public void remove(String id) { 
     MAP.remove(id); 
    } 

    public Long get(String id) { 
     return MAP.get(ID); 
    } 
} 
+0

К сожалению, я не использую Java 8, но я думаю, что даже при таком подходе моя бывшая эмблема существовала. – loicram

+0

Infact your use case - прекрасный пример для computeIfpresent(). ConcurrentHashMap гарантирует, что все операции, которые выполняются в лямбда, которые вы предоставляете, являются атомарными. Учитывая ключ, карта позволяет выполнять определенные действия над значением, это его настоящее.Вы можете уменьшить значение или даже удалить отображение, вернув null. –

0

Если вы посмотрите на источнике вы увидите, что com.google.common.util.concurrent.AtomicLongMap (я предполагаю, что вы отсылая к этому классу) внутренне использует ConcurrentHashMap<K, AtomicLong> поэтому чтение действительно зависит от свойств ConcurrentHashMap, чье JavaDoc состояние:

Операции поиска (включая get) обычно не блокируются, поэтому могут перекрываться с операциями обновления (включая put и remove). Retrievals отражают результаты последних завершенных работ по обновлению с их началом.

Однако, поскольку вы обойдете карту здесь (вы читаете, проверяете и затем уменьшаете), ваш код не является потокобезопасным. Таким образом, вам, возможно, придется синхронизировать свои методы или использовать механизм (вы можете попробовать что-то вроде AtomicLong#compareAndSet, но AtomicLongMap, похоже, не предоставляет для этого доступа).

+0

Хмм я читал документы для AtomicLongMap, чтобы проверить безопасность потока метода get, но я ничего не мог найти, я не проверял документы ConcurrentHashMap, что делает все очевидным. В основном, что мне нужно сделать, это синхронизация доступа к карте. – loicram

+0

@loicram да, вам, вероятно, придется синхронизировать доступ к карте, но с некоторыми рефакторингами вы можете попробовать и синхронизировать только с записью, т. Е. Если значение никогда не изменяется, а является объектом-оболочкой, который содержит счетчик, который вы уменьшаете, вы можете синхронизировать только для этого значения, и, таким образом, если два потока работают с разными ключами, они не будут блокироваться. Добавление нового значения может потребовать особого внимания в этом случае, хотя в этом случае вы можете использовать блокировку с двумя проверками. – Thomas