2014-02-16 3 views
0

Вот краткое описание того, что происходит -Подождите один из многих параллельных потока, чтобы завершить

Выполнение сценария:

Поисковый запрос --> Проверьте, существуют ли данные в кэше для этого запроса --> если существуют в кэше , получить его из кэша в противном случае получить его из БД и помещает данные в кэш

отлично Эта работа для следующего сценария


Вот сценарий, если запросы получены последовательно

Первый запрос Запрос кэша --> проверка (оленья кожа существовать как это первый запрос) -> получить из БД и поместить в кэш

Второй запрос Запрос --> check cache (данные существуют, поскольку предыдущий запрос уже сделал данные доступны в кеше)

Третий запрос Запрос --> проверки кэша (данные по-прежнему существует)

Четвертый запрос Запрос --> проверки кэша (данные по-прежнему существует)


однако не в состоянии, если несколько потоков просить это данных одновременно.

Вот сценарий, если запрос поступает параллельно (одновременно)

Первый запрос Запрос --> кэша проверки (оленья кожа существовать как это первый запрос) -> получить от DB и поместить в кэш

Второй запрос запрос --> кэш проверки (оленья кожа существовать как это первый запрос) -> получить из БД и положить I п кэш

Третий запрос Запрос кэша --> проверка (оленья кожа существовать как это первый запрос) -> получить из БД и поместить в кэш

Четвертый запрос Запрос --> проверки кэша (оленья кожа существуют как это первый запрос) -> получить из БД и поместить в кэш


Вы заметили проблему? каждый поток попадает в базу данных.

Я не использую какой-либо синхронизированный блок, так как это сделает его последовательным выполнением, правильно?

Как я могу избежать этой проблемы, так что только один поток попадает в базу данных, а другой выбирает данные из кеша (специально в случае параллельного выполнения)? их ли какая-либо модель уже существует для решения таких проблем?

Я знаю, что я смешал темы с запросами, но они по сути то же самое.

И не стесняйтесь изменять название этого вопроса, если оно выглядит плохо.

+0

'synchronized' это ключевое слово, чтобы отметить определенный блок кода только годной к употреблению одним потоком одновременно - идея не сделать все' synchronized', но важнейшие части. – Smutje

+0

Вы сказали _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ _ –

ответ

0

Вы можете использовать блокировку с двойной проверкой (http://en.wikipedia.org/wiki/Double-checked_locking), чтобы обеспечить запись только одного потока в кэш, а остальные используют кеш для чтения.

0

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

Вы можете получить вдохновение от Lazy Initialization holder class idiom, который решает проблему для одиночных игроков.

Как кеширование может быть сложным и полным ловушек, я бы рекомендовал использовать сторонние реализации кэша. Например, Guava cache уже обрабатывает проблемы параллелизма и должен быть вам полезен.

1

Первый поток, который видит объект в кеше, не существует, создает временный объект специального класса (вид будущего) и помещает его в хэш-таблицу. Затем запускается запрос БД.

Последующие потоки видят временный объект и не запрашивают БД, но остаются в очереди, ожидая появления результата.

Первый поток получает результат из БД и уведомляет другие темы.

Класс временного объекта может быть создан с нуля или на основе набора символов Guava SettableFuture, java8 CompletableFuture или java5 FutureTask.

Приложение

Чтобы убедиться, что только один из параллельных потоков начинает DB выборки, весь кэш должен быть заблокирован, в то время как поток проверяет кэш и вставляет временный объект. В результате потоки, которые обращаются к разным клавишам, конкурируют друг с другом. Если скорость доступа высока, и это приводит к ухудшению производительности, может помочь this solution. Он объясняет, что кэш можно тестировать параллельно. Для вашей задачи решение должно быть обновлено, чтобы извлеченные данные были помещены в кеш.

+0

_ «Первый поток, который видит объект, не существует в кеше» _ Нет, все потоки являются первыми потоками, если они запускаются в одно и то же время. –

+0

Это хорошее начало для решения, но нуждается в тонкой настройке. После промаха в кеше в кэше должен быть выполнен атомный 'putIfAbsent (обещание)', и только выигрывающий поток продолжается с извлечением БД. –

+0

@ Марко Топольник да, и я расширил свой ответ. –

0

Я действительно не уверен в какой-либо подобии вашей проблемы с ленивой инициализацией. Я думаю, что проблема может быть решена с помощью очень простого решения. Для того, чтобы сделать его параллельно можно использовать ReadWriteLocks как:

private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 
private final Lock cacheReadLock = readWriteLock.readLock(); 
private final Lock dbWriteLock = readWriteLock.writeLock(); 

При обновлении кэша из БДА используйте блокировку записи (dbWriteLock), и когда вы только чтение из кэша использовать простую блокировку чтение (cacheReadLock). Надеюсь, поможет. Не стесняйтесь комментировать, если вы хотите, чтобы я добавил еще образец кода.

0

Использовать Атомное целое [say counter] (инициализировано до нуля) и передать его всем работникам (потоку). Кого нить достигает точки кода только после проверки кэша и непосредственно перед входом в БД будет вызван что-то вроде этого:

/* some code for read from cache */ 
/* if not found in cache , then only */ 
boolean goAhead = counter.compareAndSet(0,1); 
if(goAhead) { 
      /* some code to read from db */ 
      /* some code to put into cache */ 
     synchronized(counter) { 
       counter.notifyAll(); 
     } 

} else { 
     synchronized(counter) { 
       counter.wait(); 
     } 
     /* read from cache */ 
    } 

Пожалуйста, убедитесь, что это (ждать и уведомить) код будет только дозвонились когда кеш пуст.

0

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

if (fetchFromCache() == null) { 
    synchronized(this) { // or any monitor lock of choice 
      if(fetchFromCache == null) { 
        // code to call db and fill cache data 
     } 
    } 
} 
return fetchFromCache(); // this call will never be null 

Similar implementation is explained here

+2

Хотя эта ссылка может ответить на вопрос, лучше включить основные части ответа здесь и предоставить ссылку для справки. Ответные ссылки могут стать недействительными, если связанная страница изменится. - [Из обзора] (/ review/low-quality-posts/10908740) – skypjack

+0

Спасибо за ввод. Я изменил ответ соответственно –

Смежные вопросы