Вопрос по java, concurrency – Как кэшировать информацию в DAO потокобезопасным способом

2

Мне часто приходится применять DAO для некоторых справочных данных, которые не очень часто меняются. Я иногда кеширую это в поле сбора данных в DAO - чтобы он загружался только один раз и обновлялся при необходимости

Однако это вызывает много проблем с параллелизмом - что если другой поток попытается получить доступ к данным во время загрузки или обновления.

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

Я включил тривиальный некорректный пример того, что мне нужно как соломенный работник. Пожалуйста, предложите альтернативные способы реализации этого.

public class LocationDAOImpl implements LocationDAO {

private List<Location> locations = null;

public List<Location> getAllLocations() {
    if(locations == null) {
        loadAllLocations();
    }
    return locations;
}

Для получения дополнительной информации я использую Hibernate и Spring, но это требование будет применяться ко многим технологиям.

Некоторые дальнейшие мысли:

Разве это не должно быть обработано в коде вообще - вместо этого пусть ehcache или подобное обрабатывает это? Есть ли общий шаблон для этого, который я пропускаю? Очевидно, что это может быть достигнуто многими способами, но я никогда не находил шаблон, который был бы простым и обслуживаемым.

Заранее спасибо!

Спасибо всем за отличные ответы. Каждый что-то добавил. Pablojim

Ваш Ответ

6   ответов
0

Я думаю, что лучше не делать это самостоятельно, потому что правильно понять это очень сложно. Использование EhCache или OSCache с Hibernate и Spring - гораздо лучшая идея.

Кроме того, это делает ваши DAO полными, что может быть проблематично. У вас не должно быть никакого состояния, кроме объектов подключения, фабрики или шаблона, которыми управляет Spring.

ОБНОВЛЕНИЕ: если ваши справочные данные не слишком велики и действительно никогда не меняются, возможно, альтернативным вариантом было бы создание перечислений и полное исключение из базы данных. Нет кеша, нет спящего, нет забот. Возможно, oxbow_lakes & apos; На это стоит обратить внимание: возможно, это может быть очень простая система.

Зачем вам использовать что-то вроде ehcache и hibernate для такой простой системы? Добавление зависимостей и таких тяжеловесных фреймворков (как Hibernate) - это большое решение, на мой взгляд. Я усвоил трудный путь, что этот стандартный подход может вернуться, чтобы укусить вас
Он сказал, что уже использует Hibernate, поэтому лучше использовать EhCache, чем писать свой собственный. Вопрос о том, использовать Spring или Hibernate вместо написания своего, - это другой вопрос.
2

Если вы просто хотите быстро внедрить собственное решение для кэширования, взгляните наэтот статья о JavaSpecialist, которая является рецензией на книгуJava-параллелизм на практике отБрайан Гетц.

В нем говорится о реализации базового потокового кеша с использованиемFutureTask иConcurrentHashMap.

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

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

Другая мысль о кешировании - сбор мусора. Без использования WeakHashMap для вашего кеша GC не сможет высвободить память, используемую кешем, если это необходимо. Если вы кэшируете редко используемые данные (но данные, которые все еще стоили кэшировать, поскольку их трудно вычислить), вы можете помочь сборщику мусора при нехватке памяти с помощью WeakHashMap.

Принято, как я узнал больше всего из этого, и есть определенные ситуации, когда использование готового кеш-решения не достаточно. Pablojim
3

В ситуациях, когда я свернул свой собственный кэш справочных данных, я обычно использовалReadWriteLock чтобы уменьшить конфликт нитей. Каждый из моих методов доступа принимает форму:

public PersistedUser getUser(String userName) throws MissingReferenceDataException {
    PersistedUser ret;

    rwLock.readLock().lock();
    try {
        ret = usersByName.get(userName);

        if (ret == null) {
            throw new MissingReferenceDataException(String.format("Invalid user name: %s.", userName));
        }
    } finally {
        rwLock.readLock().unlock();
    }

    return ret;
}

Единственный способ снять блокировку записиrefresh(), который я обычно выставляю через MBean:

public void refresh() {
    logger.info("Refreshing reference data.");
    rwLock.writeLock().lock();
    try {
        usersById.clear();
        usersByName.clear();

        // Refresh data from underlying data source.

    } finally {
        rwLock.writeLock().unlock();
    }
}

Кстати, я решил реализовать свой собственный кеш, потому что:

  • My reference data collections are small so I can always store them all in memory.
  • My app needs to be simple / fast; I want as few dependencies on external libraries as possible.
  • The data is rarely updated and when it is the call to refresh() is fairly quick. Hence I eagerly initialise my caches (unlike in your straw man example), which means accessors never need to take out the write lock.
6

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

0

Obviously this can be handled by making both the getters and setters of the data synchronised - but for a large web application this is quite an overhead.

I've included a trivial flawed example of what I need as a strawman. Please suggest alternative ways to implement this.

Хотя это может быть несколько верно, вы должны принять к сведению, что приведенный вами пример кода, безусловно, должен быть синхронизирован, чтобы избежать проблем параллелизма при отложенной загрузкеlocations, Если этот метод доступа не синхронизирован, то у вас будет:

  • Multiple threads access the loadAllLocations() method at the same time
  • Some threads may enter loadAllLocations() even after another thread has completed the method and assigned the result to locations - under the Java Memory Model there is no guarantee that other threads will see the change in the variable without synchronization.

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

Спасибо, Мэтт, я понимаю, что он сломан, поэтому я назвал его ошибочным. Pablojim
1

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

Похожие вопросы