public synchronized void put(K key, V value) {
        V oldValue = cache.get(key);
        if (oldValue != null && oldValue != value) {
            stats.markOverWrite(cacheName);
            cache.remove(key);
            afterRemoveValue(oldValue);
        } else {
            stats.markPut(cacheName);
        }

        cache.put(key, value);
        value.size().whenComplete((v, ex) -> {
            synchronized (AsyncLRUCache.this) {
                if (ex != null) {
                    stats.markItemCompleteExceptionally(cacheName);
                    cache.remove(key);
                } else if (!removedSet.contains(value)) {
                    completedSet.add(value);
                    if (totalSize.addAndGet(v) > maxSize) {
                        evict();
                    }
                } else {
                    try {
                        value.close();
                    } catch (Exception e) {
                        LOGGER.error("Failed to close {}", value, e);
                    }
                }
                removedSet.remove(value);
            }
        });
    }
An asynchronous LRU cache that supports asynchronous value computation.

@param <K> key type
@param <V> value type, NOTE: V must not override equals and hashCode
s3stream/src/main/java/com/automq/stream/s3/cache/AsyncLRUCache.java