2016-06-09 3 views
4

Мне нужно знать, когда я должен добавить блок синхронизации в свой код при использовании ConcurrentHashMap. Предположим, у меня есть метод, например:Когда и как следует использовать дополнительную синхронизацию ConcurrentHashMap?

private static final ConcurrentMap<String, MyObjectWrapper> myObjectsCache = new ConcurrentHashMap<>(CACHE_INITIAL_CAPACITY); 

    public List<MyObject> aMethod(List<String> ids, boolean b) { 
     List<MyObject> result = new ArrayList<>(ids.size()); 
     for (String id : ids) { 
      if (id == null) { 
       continue; 
      } 
      MyObjectWrapper myObjectWrapper = myObjectsCache.get(id); 
      if (myObjectWrapper == null) { 
       continue; 
      } 
      if (myObjectWrapper.getObject() instanceof MyObjectSub) { 
       ((MyObjectSub) myObjectWrapper.getObject()).clearAField(); 
       myObjectWrapper.getObject().setTime(System.currentTimeMillis()); 
      } 
      result.add(myObjectWrapper.getObject()); 
      if (b) { 
       final MyObject obj = new MyObject(myObjectWrapper.getObject()); 
       addObjectToDb(obj); 
      } 
     } 
     return result; 
    } 

Как я могу эффективно сделать этот метод одновременно? Я думаю, что «get» безопасен, но как только я получу значение из кеша и обновляю поля кэшированного объекта, могут возникнуть проблемы, связанные с тем, что другой поток может получить одну и ту же оболочку и попытаться обновить один и тот же базовый объект ... Должен ли я добавить синхронизацию? И если да, то следует ли мне синхронизировать с «get» до конца цикла или всего цикла?

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

я был бы очень благодарен.

EDIT: Некоторого контекст вопрос: я в настоящее время работаю над рефакторингом некоторых классов дао в производстве кода и несколько классов, используемых HashMaps для кэширования данных, извлекаемых из базы данных. Все методы, использующие кеш (для записи или чтения), содержали весь контент в блоке синхронизации (кеш) (играя безопасно?). У меня нет большого опыта параллелизма, и я действительно хочу использовать эту возможность для изучения. Я наивно изменил HashMaps на ConcurrentHashMaps и теперь хочу удалить синхронизированные блоки, где они необходимы. Все кеши используются для записи и чтения. Представленный метод основан на одном из методов, которые я изменил, и теперь я пытаюсь узнать, когда и в какой степени синхронизироваться. Методы clearAField просто изменяет значение одного из полей обернутого объекта POJO, а addObjectToDb пытается добавить объект в базу данных.

Другого примера будет заправка кэша:

public void findAll() throws SQLException{ 
    // get data from database into a list 
    List<Data> data=getAllDataFromDatabase(); 
    cacheCHM.clear(); 
    cacheCHM.putAll(data); 
} 

В этом случае я должен поставить ясный и putAll внутри синхронизации (cacheCHM) блок, не так ли?

Я пытался найти и прочитать некоторые сообщения/статьи о правильном и эффективном использовании CHM, но большинство имеют дело с одиночными операциями, без петель и т.д .... Лучшее, что я нашел бы: http://www.javamadesoeasy.com/2015/04/concurrenthashmap-in-java.html

+0

Это в значительной степени зависит от вашей логики приложения. – pintxo

+2

Если вам нужно обновить полученное значение для обеспечения потокобезопасности, вы должны синхронизировать его с самим объектом. «ConcurrentHashMap» защищает только структуру самой карты (т. Е. Отношение ключей к значениям), а не содержащиеся значения. –

+0

Я согласен с @Jim, параллельная карта будет защищать только структуру (т. Е. Отношение ключей к значениям). Я хочу добавить к нему еще одну вещь. В соответствии с приведенным выше контекстом кода вы читаете только значение ie.e myObjectsCache.get (id), поэтому для этой цели вам может даже не понадобиться параллельная карта, пока вы не вызовете map.put(). – pbajpai21

ответ

1

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

У вас есть только один звонок на ConcurrentHashMap: myObjectsCache.get(id), это нормально. Фактически, поскольку ничто не записывает данные в ваш objectCache (см. Предположение выше), вам даже не нужна ConcurrentHashMap! С тобой все будет в порядке с любой неизменной коллекцией. У вас подозрительная строка в конце: addObjectToDb(obj), этот метод также влияет на ваш кеш? Если это так, то все равно безопасно (возможно, нам нужно будет убедиться, что метод будет определен), но вам определенно нужен ConcurentHashMap.

Опасность заключается в том, где вы изменяете объекты, здесь:

myObjectWrapper.getObject().clearAField(); 
myObjectWrapper.getObject().setTime(System.currentTimeMillis()); 

Это возможно для нескольких потоков, чтобы вызывать эти методы и тот же объект одновременно. Не зная, что делают эти методы, мы не можем сказать, безопасно это или нет.Если эти методы отмечены как синхронизированные, или если вы позаботились о том, чтобы эти методы могли безопасно работать одновременно, тогда вы в порядке (но будьте осторожны, чтобы эти методы могли работать в разных порядках, чтобы вы могли интуитивно ожидать!). Если вы не были настолько осторожны, тогда существует потенциал для искажения данных.

+0

Извините, что не очень конкретно. У меня есть карты кеша в некоторых из моих классов dao, в которых хранятся данные, полученные из базы данных sql. Эти кэши записываются и считываются с помощью некоторых манипуляций с кэшированными объектами. Классы использовали HashMaps для кэшей и синхронизировали весь контент методов, которые использовали кеш с блокировкой на карте. Я хочу реорганизовать код, чтобы он был более эффективным и поддерживал сплоченность кеша. Методы clearAField и setTime из примера не синхронизированы, это простые сеттеры pojo. – ssr

+0

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

+0

Итак, я должен синхронизировать все неатомные операции с моим кешем, и если я изменил кешированные объекты после их возврата (удаленные объекты тоже считаются или только «get»)? Я должен включить этот код в блок синхронизации (я Думаю, я мог бы сделать кешированные объекты неизменными)? Как вы видите, я ищу кого-то, чтобы вести меня за руку ... потому что я должен быть уверен, что мои изменения будут действительны. – ssr

0

Лучшим подходом к threadsaftey и кешам является использование immutable objects. Вот что MyObjectSub Calss может выглядеть, как если бы это были неизменны [не знает, почему вам нужна обертка - я бы опустить, что вполне возможно]:

//Provided by way of example. You should consider generating these 
//using http://immutables.github.io/ or similar 
public class MyImmutableObject { 
    //If all members are final primitives or immutable objects 
    //then this class is threadsafe. 
    final String field; 
    final long time; 

    public MyImmutableObject(String field, long time) { 
     this.field = field; 
     this.time = time; 
    } 

    public MyImmutableObject clearField() { 
     //Since final fields can never be changed, our only option is to 
     //return a copy. 
     return new MyImmutableObject("", this.time); 
    } 

    public MyImmutableObject setTime(long newtime) { 
     return new MyImmutableObject(this.field, newtime); 
    } 
} 

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

public List<Result> typicialCacheUsage(String key) { 
    MyImmutableObject obj = myObjectsCache.get(key); 

    obj = obj.clearField(); 
    obj = obj.setTime(System.currentTimeMillis()); 

    //If you need to put the object back in the cache you can do this: 
    myObjectsCache.put(key, obj); 

    List<Result> res = generateResultFromObject(obj); 
    return res; 
} 
+0

Было бы лучше, если бы вы добавили это в свой существующий ответ, а не отправляли его как отдельный - это не ответ на вопрос. – Hulk