Skip to content

RedisCache.get(Object, Callable) synchronizes on entire cache instead of individual keys #2890

Closed
@Traubenfuchs

Description

@Traubenfuchs

The following code can be found in org.springframework.data.redis.cache.RedisCache.

	private final Lock lock = new ReentrantLock();

	@Override
	@SuppressWarnings("unchecked")
	public <T> T get(Object key, Callable<T> valueLoader) {

		ValueWrapper result = get(key);

		return result != null ? (T) result.get() : getSynchronized(key, valueLoader);
	}

	@Nullable
	@SuppressWarnings("unchecked")
	private <T> T getSynchronized(Object key, Callable<T> valueLoader) {

		lock.lock();

		try {
			ValueWrapper result = get(key);
			return result != null ? (T) result.get() : loadCacheValue(key, valueLoader);
		} finally {
			lock.unlock();
		}
	}

I believe there could be a significant amount of unnecessary locking happening here.

I don't think that this method should be locked via an instance-global ReentrantLock instance.

Instead, it should be locked on a per key basis, as follows:

        private final ConcurrentHashMap<Object, Lock> keyToLock = new ConcurrentHashMap<>();

	@Nullable
	@SuppressWarnings("unchecked")
	private <T> T getSynchronized(Object key, Callable<T> valueLoader) {

                Lock lock = keyToLock.computeIfAbsent(key, k -> new ReentrantLock());

		lock.lock();

		try {
			ValueWrapper result = get(key);
			return result != null ? (T) result.get() : loadCacheValue(key, valueLoader);
		} finally {
			lock.unlock();
		}
	}

Plus additional cleanup calls to the ConcurrentHashMap for clear and evict calls to RedisCache.

What do you think?

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions