Skip to content

Commit 713a112

Browse files
bananayongbclozel
authored andcommitted
Improve ConcurrentLruCache performance
- manage collection size manually - check cache hit first before size check - reduce read-lock scope - use `map.get` to test cache instead of `queue.remove` Closes gh-24469 See gh-24671
1 parent 7e7e54b commit 713a112

File tree

1 file changed

+39
-19
lines changed

1 file changed

+39
-19
lines changed

spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.Random;
3131
import java.util.concurrent.ConcurrentHashMap;
3232
import java.util.concurrent.ConcurrentLinkedQueue;
33+
import java.util.concurrent.locks.Lock;
3334
import java.util.concurrent.locks.ReadWriteLock;
3435
import java.util.concurrent.locks.ReentrantReadWriteLock;
3536
import java.util.function.Function;
@@ -420,54 +421,73 @@ private static class ConcurrentLruCache<K, V> {
420421

421422
private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
422423

423-
private final ReadWriteLock lock = new ReentrantReadWriteLock();
424+
private final Lock readLock;
425+
426+
private final Lock writeLock;
424427

425428
private final Function<K, V> generator;
426429

430+
private volatile int size = 0;
431+
427432
public ConcurrentLruCache(int maxSize, Function<K, V> generator) {
428433
Assert.isTrue(maxSize > 0, "LRU max size should be positive");
429434
Assert.notNull(generator, "Generator function should not be null");
430435
this.maxSize = maxSize;
431436
this.generator = generator;
437+
438+
ReadWriteLock lock = new ReentrantReadWriteLock();
439+
this.readLock = lock.readLock();
440+
this.writeLock = lock.writeLock();
432441
}
433442

434443
public V get(K key) {
435-
this.lock.readLock().lock();
436-
try {
437-
if (this.queue.size() < this.maxSize / 2) {
438-
V cached = this.cache.get(key);
439-
if (cached != null) {
440-
return cached;
441-
}
444+
V cached;
445+
446+
if ((cached = this.cache.get(key)) != null) {
447+
if (this.size < this.maxSize / 2) {
448+
return cached;
442449
}
443-
else if (this.queue.remove(key)) {
450+
451+
try {
452+
this.readLock.lock();
444453
this.queue.add(key);
445-
return this.cache.get(key);
454+
this.queue.remove(key);
455+
return cached;
456+
}
457+
finally {
458+
this.readLock.unlock();
446459
}
447460
}
448-
finally {
449-
this.lock.readLock().unlock();
450-
}
451-
this.lock.writeLock().lock();
461+
462+
this.writeLock.lock();
452463
try {
453464
// retrying in case of concurrent reads on the same key
454-
if (this.queue.remove(key)) {
465+
if ((cached = this.cache.get(key)) != null) {
455466
this.queue.add(key);
456-
return this.cache.get(key);
467+
this.queue.remove(key);
468+
return cached;
457469
}
458-
if (this.queue.size() == this.maxSize) {
470+
471+
// Generate value first, to prevent size inconsistency
472+
V value = this.generator.apply(key);
473+
474+
int cacheSize = this.size;
475+
if (cacheSize == this.maxSize) {
459476
K leastUsed = this.queue.poll();
460477
if (leastUsed != null) {
461478
this.cache.remove(leastUsed);
479+
cacheSize--;
462480
}
463481
}
464-
V value = this.generator.apply(key);
482+
465483
this.queue.add(key);
466484
this.cache.put(key, value);
485+
this.size = cacheSize + 1;
486+
467487
return value;
468488
}
469489
finally {
470-
this.lock.writeLock().unlock();
490+
this.writeLock.unlock();
471491
}
472492
}
473493
}

0 commit comments

Comments
 (0)