После реализации этого базового кэширования в ходе дальнейшего тестирования я обнаружил, что не учел, как параллельные потоки могут одновременно обращаться к кешу. Я обнаружил, что в течение примерно 100 мс около 50 потоков пытались получить объект из кэша, обнаруживали, что срок его действия истек, обращались к веб-службе для получения данных, а затем помещали объект обратно в кэш.
Исходный код выглядел примерно так:
Код: Выделить всё
private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
final String key = "Data-" + email;
SomeData[] data = (SomeData[]) StaticCache.get(key);
if (data == null) {
data = service.getSomeDataForEmail(email);
StaticCache.set(key, data, CACHE_TIME);
}
else {
logger.debug("getSomeDataForEmail: using cached object");
}
return data;
}
Я обновил метод, чтобы он выглядел следующим образом:
/>
Код: Выделить всё
private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
SomeData[] data = null;
final String key = "Data-" + email;
synchronized(key) {
data =(SomeData[]) StaticCache.get(key);
if (data == null) {
data = service.getSomeDataForEmail(email);
StaticCache.set(key, data, CACHE_TIME);
}
else {
logger.debug("getSomeDataForEmail: using cached object");
}
}
return data;
}
Однако, похоже, это не сработало. В моих журналах тестирования есть такие данные:
Код: Выделить всё
(log output is 'threadname' 'logger name' 'message')
http-80-Processor253 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor253 jsp.view-page - getSomeDataForEmail: inside synchronization block
http-80-Processor253 cache.StaticCache - get: object at key [SomeData-test@test.com] has expired
http-80-Processor253 cache.StaticCache - get: key [SomeData-test@test.com] returning value [null]
http-80-Processor263 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor263 jsp.view-page - getSomeDataForEmail: inside synchronization block
http-80-Processor263 cache.StaticCache - get: object at key [SomeData-test@test.com] has expired
http-80-Processor263 cache.StaticCache - get: key [SomeData-test@test.com] returning value [null]
http-80-Processor131 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor131 jsp.view-page - getSomeDataForEmail: inside synchronization block
http-80-Processor131 cache.StaticCache - get: object at key [SomeData-test@test.com] has expired
http-80-Processor131 cache.StaticCache - get: key [SomeData-test@test.com] returning value [null]
http-80-Processor104 jsp.view-page - getSomeDataForEmail: inside synchronization block
http-80-Processor104 cache.StaticCache - get: object at key [SomeData-test@test.com] has expired
http-80-Processor104 cache.StaticCache - get: key [SomeData-test@test.com] returning value [null]
http-80-Processor252 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor283 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor2 jsp.view-page - getSomeDataForEmail: about to enter synchronization block
http-80-Processor2 jsp.view-page - getSomeDataForEmail: inside synchronization block
Есть ли проблемы с синхронизацией объектов String? Я подумал, что ключ кэша будет хорошим выбором, поскольку он уникален для операции, и хотя последний строковый ключ объявлен внутри метода, я думал, что каждый поток будет получать ссылку на один и тот же объект и, следовательно, будет синхронизироваться с этим единственным объектом.
Что я здесь делаю не так?
Обновление: после дальнейшего просмотра журналов кажется, что это методы с одинаковой логикой синхронизации, где ключ всегда одно и то же, например
Код: Выделить всё
final String key = "blah";
...
synchronized(key) { ...
Обновление 2: Спасибо всем за помощь! Я принял первый ответ о строках intern(), который решил мою первоначальную проблему - когда несколько потоков входили в синхронизированные блоки там, где, как я думал, они не должны, потому что ключ имел одно и то же значение.
Как отмечали другие, использование intern() для такой цели и синхронизация этих строк действительно оказывается плохой идеей - при запуске тестов JMeter для веб-приложения для имитации ожидаемой нагрузки я увидел использованный размер кучи увеличивается почти до 1 ГБ менее чем за 20 минут.
В настоящее время я использую простое решение, заключающееся в простой синхронизации всего метода, но мне действительно нравятся примеры кода, предоставленные martinprobst и MBCook, но поскольку в настоящее время в этом классе у меня около 7 похожих методов getData() (поскольку ему требуется около 7 различных фрагментов данных из веб-службы), я не хотел добавлять почти дубликаты логика получения и снятия блокировок для каждого метода. Но это определенно очень и очень ценная информация для будущего использования. Я думаю, что это в конечном итоге правильные ответы о том, как лучше всего сделать такую операцию потокобезопасной, и я бы отдал больше голосов за эти ответы, если бы мог!
Подробнее здесь: https://stackoverflow.com/questions/133 ... ts-in-java
Мобильная версия