diff --git a/spring-core/src/main/java/org/springframework/util/ConcurrentLruCache.java b/spring-core/src/main/java/org/springframework/util/ConcurrentLruCache.java index 1af6d2e5aed..2f1f2e08965 100644 --- a/spring-core/src/main/java/org/springframework/util/ConcurrentLruCache.java +++ b/spring-core/src/main/java/org/springframework/util/ConcurrentLruCache.java @@ -105,17 +105,15 @@ public class ConcurrentLruCache { } // Generate value first, to prevent size inconsistency V value = this.generator.apply(key); - int cacheSize = this.size; - if (cacheSize == this.sizeLimit) { + if (this.size == this.sizeLimit) { K leastUsed = this.queue.poll(); if (leastUsed != null) { this.cache.remove(leastUsed); - cacheSize--; } } this.queue.offer(key); this.cache.put(key, value); - this.size = cacheSize + 1; + this.size = this.cache.size(); return value; } finally { @@ -123,6 +121,50 @@ public class ConcurrentLruCache { } } + /** + * Determine whether the given key is present in this cache. + * @param key the key to check for + * @return {@code true} if the key is present, + * {@code false} if there was no matching key + */ + public boolean contains(K key) { + return this.cache.containsKey(key); + } + + /** + * Immediately remove the given key and any associated value. + * @param key the key to evict the entry for + * @return {@code true} if the key was present before, + * {@code false} if there was no matching key + */ + public boolean remove(K key) { + this.lock.writeLock().lock(); + try { + boolean wasPresent = (this.cache.remove(key) != null); + this.queue.remove(key); + this.size = this.cache.size(); + return wasPresent; + } + finally { + this.lock.writeLock().unlock(); + } + } + + /** + * Immediately remove all entries from this cache. + */ + public void clear() { + this.lock.writeLock().lock(); + try { + this.cache.clear(); + this.queue.clear(); + this.size = 0; + } + finally { + this.lock.writeLock().unlock(); + } + } + /** * Return the current size of the cache. * @see #sizeLimit() diff --git a/spring-core/src/test/java/org/springframework/util/ConcurrentLruCacheTests.java b/spring-core/src/test/java/org/springframework/util/ConcurrentLruCacheTests.java new file mode 100644 index 00000000000..31ec75944f3 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/util/ConcurrentLruCacheTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.util; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Juergen Hoeller + */ +class ConcurrentLruCacheTests { + + private final ConcurrentLruCache cache = new ConcurrentLruCache<>(2, key -> key + "value"); + + + @Test + void getAndSize() { + assertThat(this.cache.sizeLimit()).isEqualTo(2); + assertThat(this.cache.size()).isEqualTo(0); + assertThat(this.cache.get("k1")).isEqualTo("k1value"); + assertThat(this.cache.size()).isEqualTo(1); + assertThat(this.cache.contains("k1")).isTrue(); + assertThat(this.cache.get("k2")).isEqualTo("k2value"); + assertThat(this.cache.size()).isEqualTo(2); + assertThat(this.cache.contains("k1")).isTrue(); + assertThat(this.cache.contains("k2")).isTrue(); + assertThat(this.cache.get("k3")).isEqualTo("k3value"); + assertThat(this.cache.size()).isEqualTo(2); + assertThat(this.cache.contains("k1")).isFalse(); + assertThat(this.cache.contains("k2")).isTrue(); + assertThat(this.cache.contains("k3")).isTrue(); + } + + @Test + void removeAndSize() { + assertThat(this.cache.get("k1")).isEqualTo("k1value"); + assertThat(this.cache.get("k2")).isEqualTo("k2value"); + assertThat(this.cache.size()).isEqualTo(2); + assertThat(this.cache.contains("k1")).isTrue(); + assertThat(this.cache.contains("k2")).isTrue(); + this.cache.remove("k2"); + assertThat(this.cache.size()).isEqualTo(1); + assertThat(this.cache.contains("k1")).isTrue(); + assertThat(this.cache.contains("k2")).isFalse(); + assertThat(this.cache.get("k3")).isEqualTo("k3value"); + assertThat(this.cache.size()).isEqualTo(2); + assertThat(this.cache.contains("k1")).isTrue(); + assertThat(this.cache.contains("k2")).isFalse(); + assertThat(this.cache.contains("k3")).isTrue(); + } + + @Test + void clearAndSize() { + assertThat(this.cache.get("k1")).isEqualTo("k1value"); + assertThat(this.cache.get("k2")).isEqualTo("k2value"); + assertThat(this.cache.size()).isEqualTo(2); + assertThat(this.cache.contains("k1")).isTrue(); + assertThat(this.cache.contains("k2")).isTrue(); + this.cache.clear(); + assertThat(this.cache.size()).isEqualTo(0); + assertThat(this.cache.contains("k1")).isFalse(); + assertThat(this.cache.contains("k2")).isFalse(); + assertThat(this.cache.get("k3")).isEqualTo("k3value"); + assertThat(this.cache.size()).isEqualTo(1); + assertThat(this.cache.contains("k1")).isFalse(); + assertThat(this.cache.contains("k2")).isFalse(); + assertThat(this.cache.contains("k3")).isTrue(); + } + +}