У меня есть webapp, что я нахожусь в середине тестирования нагрузки и производительности, особенно в отношении функции, где мы ожидаем, что несколько сотен пользователей будут получать доступ к одной и той же странице и получать обновления о каждом 10 секунд на этой странице. Одной из областей улучшения, которую мы обнаружили с помощью этой функции, было кэширование ответов веб-службы в течение некоторого периода времени, поскольку данные не изменяются.Синхронизация по объектам String в Java
После реализации этого базового кэширования в ходе дальнейшего тестирования выяснилось, что я не рассматривал возможность одновременного доступа к кэшу одновременно. Я обнаружил, что в течение ~ 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;
}
Таким образом, чтобы убедиться, что только один поток вызова веб-службы, когда объект в key
истек, я думал, что мне нужно, чтобы синхронизировать кэш получить/и, казалось бы, использование ключа кеша было бы хорошим кандидатом для синхронизации объекта (таким образом, вызовы этого метода для электронной почты [email protected] не будут блокироваться вызовами методов на [email protected]).
Я обновил способ выглядеть следующим образом:
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;
}
Я также добавил регистрации линий для вещей, как «перед блоком синхронизации», «внутри блока синхронизации», «уходить блок синхронизации», и " после блока синхронизации ", поэтому я мог бы определить, была ли я эффективно синхронизировать операцию get/set.
Однако похоже, что это не сработало. Мои тестовые журналы имеют выход как:
(журнал выход 'threadname' 'имя регистратора' 'сообщение')
HTTP-80-Processor253 jsp.view-страница - getSomeDataForEmail: собирается ввести блок синхронизации
http-80-Processor253 jsp.view-page - getSomeDataForEmail: внутренний блок синхронизации
http-80-Processor253 cache.StaticCache - get: объект у ключа [[email protected]] истек
http-80-Processor253 cache.StaticCache - get: key [[email protected]] возвращающее значение [null]
http-80-Processor263 jsp.view-page - getSomeDataForEmai l: собирается ввести блок синхронизации
http-80-Processor263 jsp.view-page - getSomeDataForEmail: внутренний блок синхронизации
http-80-Processor263 cache.StaticCache - get: object at key [[email protected]] истек
http-80-Processor263 cache.StaticCache - get: key [[email protected]] возвращающее значение [null]
http-80-Processor131 jsp.view-page - getSomeDataForEmail: собирается войти в блок синхронизации
http-80-Processor131 jsp.view-page - getSomeDataForEmail: внутренний блок синхронизации
http-80-Processor131 cache.StaticCache - get: объект у ключа [[email protected]] истек
HTTP-80-Processor131 cache.StaticCache - получить: ключ [[email protected]] возвращая значение [NULL]
HTTP-80-Processor104 jsp.view-страница - getSomeDataForEmail: внутри блока синхронизации
HTTP-80 -Processor104.StaticCache - get: объект в ключе [[email protected]] истек
http-80-Processor104 cache.StaticCache - get: key [[email protected]] возвращающее значение [null]
http- 80-Processor252 jsp.view-страница - getSomeDataForEmail: около ввести блок синхронизации
HTTP-80-Processor283 jsp.view-страница - getSomeDataForEmail: около ввести блок синхронизации
HTTP-80-Processor2 jsp.view-страница - getSomeDataForEmail : около ввести блок синхронизации
HTTP-80-Processor2 jsp.view-страница - getSomeDataForEmail: внутри блока синхронизации
Я хотел видеть только один поток за один раз, входящий/выходящий из блока синхронизации вокруг операций get/set.
Есть ли проблема в синхронизации объектов String? Я думал, что ключ кеша будет хорошим выбором, поскольку он уникален для операции, и хотя в этом методе объявлен final String key
, я думал, что каждый поток будет получать ссылку на на тот же объект и, следовательно, будет синхронизация на этом одном объекте.
Что я здесь делаю неправильно?
Update: после того, смотря в журналах, похоже, методы с той же логикой синхронизации где ключ всегда одинаков, таких как
final String key = "blah";
...
synchronized(key) { ...
не проявляют такую же проблему параллелизма - только один поток за один раз входит в блок.
Обновление 2: Спасибо всем за помощь! Я принял первый ответ около intern()
ing Strings, который решил мою первоначальную проблему - когда несколько потоков входили в синхронизированные блоки, где я думал, что они не должны, потому что key
имели такое же значение.
Как указывали другие, использование intern()
для такой цели и синхронизация на этих строках действительно оказывается плохой идеей - при запуске тестов JMeter против webapp для имитации ожидаемой нагрузки я видел использованный размер кучи вырастают почти до 1 ГБ чуть менее 20 минут.
В настоящее время я использую простое решение только синхронизации весь метод - но я действительно как образцы кода, предоставляемых martinprobst и MBCook, но так как у меня есть около 7 подобных getData()
методов в этом классе в настоящее время (с ней требуется около 7 различных частей данных из веб-службы), я не хотел добавлять почти дублированную логику о получении и освобождении блокировок для каждого метода. Но это определенно очень, очень ценная информация для будущего использования. Я думаю, что это, в конечном счете, правильные ответы на то, как лучше всего сделать такую операцию, как эта потокобезопасная, и я бы дал больше голосов на эти ответы, если бы мог!
Вам больше не нужно беспокоиться о стажера строки висит вокруг в памяти - Видимо intern'd струны были GC'd в течение достаточно долгого времени теперь: HTTP: // stackoverflow.com/questions/18152560/garbage-collection-on-internd-strings-string-pool-and-perm-space – Volksman 2017-02-16 17:07:29
Я рекомендую этот ответ, используя Guass's Striped, чтобы избежать чрезмерного использования памяти: https://stackoverflow.com/a/11125602/116810 –
2018-02-24 00:09:18