Почему @Cacheable и synchronized не защищают друг друга

Этим вопросом проверяют, понимает ли кандидат, что кэш-прокси Spring и синхронизация метода — механизмы из разных слоёв, которые не знают друг о друге.

@Cacheable оборачивает бин в proxy. Proxy проверяет кэш до вызова метода, и если ключа нет — вызывает реальный метод. Если этот метод помечен synchronized, блокировка защищает только сам вызов метода — она никак не мешает второму потоку одновременно пройти проверку кэша в proxy и тоже пойти внутрь.

В результате при холодном кэше и всплеске одновременных запросов несколько потоков проходят проверку «в кэше пусто» почти синхронно, и все они выполняют дорогой метод — тот самый cache stampede, от которого @Cacheable вроде должен защищать.

@Cacheable("products") public synchronized Product findById(Long id) { return repository.findExpensive(id); }

Спросят следом: как правильно защититься от stampede. Правильный ответ — не synchronized на методе, а либо sync = true в самой аннотации (@Cacheable(value = "products", sync = true)), либо явный лок по ключу перед вычислением значения. sync = true заставляет Spring использовать внутренний лок именно на уровне кэша, а не на уровне бина.

synchronized защищает вызов метода. Кэш защищает данные. Это не одно и то же, и путать их — способ получить лавину одинаковых запросов в БД при холодном старте.

Тренажёр: 600 вопросов, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки