Синхронизация объектов String в JavaJAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Синхронизация объектов String в Java

Сообщение Anonymous »

У меня есть веб-приложение, над которым я сейчас провожу тестирование нагрузки и производительности, особенно функцию, в которой мы ожидаем, что несколько сотен пользователей будут получать доступ к одной и той же странице и нажимать кнопку обновления примерно каждые 10 секунд на этой странице. Одной из областей улучшения, которую, как мы обнаружили, мы могли бы внести с помощью этой функции, было кэширование ответов от веб-службы в течение некоторого периода времени, поскольку данные не меняются.
После реализации этого базового кэширования в ходе дальнейшего тестирования я обнаружил, что не учел, как параллельные потоки могут одновременно обращаться к кешу. Я обнаружил, что в течение примерно 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;
}
Итак, чтобы гарантировать, что только один поток вызывал веб-службу, когда срок действия объекта в ключе истек, я подумал, что мне нужно синхронизировать операцию получения/установки кэша, и мне показалось, что использование ключа кэша было бы хорошим кандидатом для синхронизации объекта (таким образом, вызовы этого метода для электронной почты b@b.com не будут блокироваться вызовами метода a@a.com).
Я обновил метод, чтобы он выглядел следующим образом:
/>

Код: Выделить всё

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
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»