совместное использование одного экземпляра хэш-карты между всеми экземплярами класса обслуживания Spring

Я намерен создать счетчик реального времени. Таким образом, один пользователь может увеличить значение счетчика для определенного ключа. В то время как другой получает обновленное значение счетчика через запрос ajax (либо в цикле, либо с использованием какого-либо метода длительного опроса). Я буду использовать контроллер Spring, который будет внедрять класс обслуживания. Могу ли я сделать что-то вроде ниже или есть лучший способ:

@Service
public MyService{

//instance variable in spring injected service class, not sure if this correct
static final Map<String, Integer> myMap;


public void add(String key){
  Integer count = myMap.get(key);
  count++;
  myMap.put(key, count);
}

//accessed via ajax loop (and controller), if value changes update display
public Integer getCount(String key){
  return myMap.get(key)
}

@PostConstruct
public load(){
  myMap = new HashMap<String, Integer>(10){{//initialize}};
}

Изменить есть несколько ответов, но неясно, какой из них лучше: Синхронизировать метод добавления? Создайте карту в другом классе (аннотированный репозиторий) и введите ее? Что-то другое ?


person NimChimpsky    schedule 20.06.2012    source источник


Ответы (3)


Вы можете, но должны знать об этих проблемах:

  • изначально карта пуста, но вы никогда не проверяете нулевые счетчики;
  • метод add() не изменяет счетчик на карте. Вам нужно вернуть счетчик на карту после его увеличения, поскольку Integer неизменяем. Или вам нужно хранить изменяемые счетчики внутри карты
  • несколько потоков обращаются к карте без какой-либо синхронизации, что приведет к ошибкам, неустойчивому поведению или исключениям.
  • эта стратегия, очевидно, потерпит неудачу, если ваше приложение кластеризовано между несколькими серверами.
person JB Nizet    schedule 20.06.2012
comment
Я отредактировал мелкие проблемы, но как лучше всего решить, что несколько потоков обращаются к карте без какой-либо синхронизации, что приведет к ошибкам, неустойчивому поведению или исключениям. - person NimChimpsky; 20.06.2012
comment
В реальном приложении есть больше, чем основной поток, который может получить доступ к вашим общим ресурсам (особенно в веб-приложениях), поэтому один поток может поместить объект A с ключевым ключом, в то время как другой поток будет вставлять объект B с ключевым ключом в то же самое время. время. В случае карты вы можете использовать java. util.concurrent.ConcurrentHashMap для решения этих проблем. - person Luiggi Mendoza; 20.06.2012
comment
Правильное использование ConcurrentMap, хранящего экземпляры AtomicInteger, является решением. Но вам действительно нужно понять все потенциальные проблемы с потоками, прежде чем что-либо внедрять. Прочтите Параллелизм в Java на практике. - person JB Nizet; 20.06.2012
comment
@JBNizet Я не могу просто создать класс, чтобы обернуть карту, аннотировать как репозиторий и внедрить это? - person NimChimpsky; 20.06.2012
comment
Да, вы можете, как я сказал в своем ответе. Но поскольку к bean-компоненту будут обращаться несколько потоков, вы должны сделать его потокобезопасным и корректным. - person JB Nizet; 20.06.2012
comment
@JBNizet Хорошо, как насчет этого: stackoverflow.com/q/11126684/106261 спасибо за помощь, кстати - person NimChimpsky; 20.06.2012

Использовать ConcurrentHashMap

public void add(String key){
    Integer count = myMap.get(key);
    count= count++;
    myMap.put(key, count);
}
person Subin Sebastian    schedule 20.06.2012
comment
Это все еще неправильный код. У него нет проблем с синхронизацией, но у вас может быть три потока, одновременно вызывающих метод добавления, и значение увеличивается только на 1. - person JB Nizet; 20.06.2012
comment
да вы правы. Я думаю, нужно сделать метод синхронизированным? - person Subin Sebastian; 20.06.2012
comment
Это способ сделать код правильным, при условии, что другие методы доступа к карте также синхронизированы. Но если это будет сделано, использование ConcurrentMap не будет иметь никаких преимуществ перед использованием простого HashMap. - person JB Nizet; 20.06.2012
comment
метод синхронизации службы - это то, чего мы должны избегать - person Subin Sebastian; 20.06.2012

Класс Integer является неизменяемым. Это означает, что вы не можете вносить в него изменения. Итак, чтобы увеличить счетчик, вы должны поместить его обратно на карту после того, как вы его увеличили:

public void add(String key){
  Integer count = myMap.get(key);
  count++;
  myMap.put(key, count);
}

Возникающая при этом проблема — потокобезопасность. Если к этому классу обслуживания будут обращаться несколько потоков одновременно, вы должны убедиться, что доступ к его данным осуществляется безопасным способом. Поскольку myMap модифицируется, а класс HashMap не является потокобезопасным, вы должны сделать его потокобезопасным. Один из способов сделать это — использовать метод Collections.synchronizedMap(). Это автоматически сделает экземпляр Map потокобезопасным.

@PostConstruct
public load(){
  myMap = new HashMap<String, Integer>(10){{//initialize}};
  myMap = Collections.synchronizedMap(myMap);
}
person Michael    schedule 20.06.2012
comment
Это сделает Map потокобезопасным, но неатомарный add() метод по-прежнему не является потокобезопасным. - person nicholas.hauschild; 20.06.2012
comment
@nicholas.hauschild О да, это правда. Вам нужно будет сделать метод add() синхронизированным или обернуть его тело в блок synchronized(myMap). - person Michael; 20.06.2012