2012-06-17 3 views
0

Скажем, у вас есть такой код:Создание объекта для кеша, «асинхронно» (вид), в Java: howto?

public final class SomeClass 
{ 
    private final Map<SomeKey, SomeValue> map = new HashMap<SomeKey, SomeValue>(); 

    // ... 

    public SomeValue getFromCache(final SomeKey key) 
    { 
     SomeKey ret; 
     synchronized(map) { 
      ret = map.get(key); 
      if (ret == null) { 
       ret = buildValue(key); 
       map.put(key, ret); 
      } 
     } 
     return ret; 
    } 

//etc 
} 

Проблема заключается в производительности: если buildValue() является дорогостоящей функцией, то один абонент, имеющий построить его значение будет блокировать все остальные абоненты, стоимость которых может уже существовать. Я хотел бы найти механизм, в котором вызывающий должен построить значение, будет не заблокировать других абонентов.

Не могу поверить, что эта проблема не была решена (и решена) уже. Я попытался найти решение для решения проблемы, но не смог найти его. У вас есть ссылка, чтобы сделать это?

Я думал об использовании ReentrantReadWriteLock, но ничего не мог придумать.

ответ

1

Guava имеет очень твердое решение этой проблемы, на основе определенной работы Doug Lea, который написал большую часть из java.util.concurrent. (Раскрытие информации: Я способствовать гуавы, хотя я не работал на кэширование вообще.)

Руководство пользователя статья на Cache пакет гуавы является here, но синтаксис выглядит следующим образом ...

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder() 
    .maximumSize(1000) 
    .expireAfterWrite(10, TimeUnit.MINUTES) 
    .removalListener(MY_LISTENER) 
    .build(
     new CacheLoader<Key, Graph>() { 
     public Graph load(Key key) throws AnyException { 
      return createExpensiveGraph(key); 
     } 
     }); 
+0

Это выглядит очень интересно! У меня уже есть Guava как зависимость, поэтому я обязательно попробую это. Спасибо за подсказку! – fge

+0

Действительно. Репликация этого с помощью «ConcurrentHashMap» действительно сложна, просто потому, что 'putIfAbsent' заставляет вас заплатить стоимость вычисления значения уже, а альтернатива представляет собой причудливый цикл do-while, который все же может оказаться дублированным. –

1

Я думаю, что часть проблемы заключается в том, что метод get, который у вас есть, является синхронным. Это само по себе затрудняет выполнение каких-либо асинхронных действий. Кажется, что ваш get должен принять обратный вызов.

Java-параллелизм в практике книге подробно удивительный кэш с помощью ConcurrentHashMap и FutureTasks - проверить http://jcip.net/listings/Memoizer.java

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

+0

Отлично ~ Добавить FutureTask, поэтому вычисление не будет повторяться для одного и того же ключа, довольно удивительно! –

0

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

Другой метод заключается в использовании java.util.concurrent.ConcurrentMap, то вам не нужно иметь какую-либо блокировку с помощью putIfAbsent, когда вы положили данные в карту. Недостатком является то, что данные с одним и тем же ключом создаются одновременно в разных потоках, но это не будет проблемой, если ваше приложение не требует строгого использования памяти.