The cache is built on top of ConcurrentHashMap
so most of its behavior applies. That includes lock-free reads and fine-grained locking for writes. A computeIfAbsent style of call is possibly a read or a write depending on if the entry is present so it may block. The asMap()
call is simply a view so there is no waiting or overhead to obtain it.
An eviction is performed through a Map.compute
, which will block other writes for that entry. While the computation is in progress a read will observe the entry and won't after it completes.
A read will validate that an entry is not expired and if it is then simulate a cache miss to avoid returning a stale result. If this is a computeIfAbsent
type of call then its loader will replace the expired entry, handle notifications, etc. As another thread may in parallel be trying to evict it, the per-entry locking will make it all atomic and the loser handles that gracefully.
An expired entry is not usable, so it will cause blocking if a loading call. The refreshAfterWrite
setting can be combined with expiration to hide the penalty of active content having a periodic latency spike as calls wait on a fresh load. When an entry is eligible to refresh, but not yet expired, then the next subsequent read will trigger a background reload. If instead the entry is not accessed within the refresh interval then it will expire and be evicted. This way active content stays fresh and fast, while allowing inactive content to fade away.