|
30 | 30 | import java.util.Random;
|
31 | 31 | import java.util.concurrent.ConcurrentHashMap;
|
32 | 32 | import java.util.concurrent.ConcurrentLinkedQueue;
|
| 33 | +import java.util.concurrent.locks.Lock; |
33 | 34 | import java.util.concurrent.locks.ReadWriteLock;
|
34 | 35 | import java.util.concurrent.locks.ReentrantReadWriteLock;
|
35 | 36 | import java.util.function.Function;
|
@@ -420,54 +421,73 @@ private static class ConcurrentLruCache<K, V> {
|
420 | 421 |
|
421 | 422 | private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
|
422 | 423 |
|
423 |
| - private final ReadWriteLock lock = new ReentrantReadWriteLock(); |
| 424 | + private final Lock readLock; |
| 425 | + |
| 426 | + private final Lock writeLock; |
424 | 427 |
|
425 | 428 | private final Function<K, V> generator;
|
426 | 429 |
|
| 430 | + private volatile int size = 0; |
| 431 | + |
427 | 432 | public ConcurrentLruCache(int maxSize, Function<K, V> generator) {
|
428 | 433 | Assert.isTrue(maxSize > 0, "LRU max size should be positive");
|
429 | 434 | Assert.notNull(generator, "Generator function should not be null");
|
430 | 435 | this.maxSize = maxSize;
|
431 | 436 | this.generator = generator;
|
| 437 | + |
| 438 | + ReadWriteLock lock = new ReentrantReadWriteLock(); |
| 439 | + this.readLock = lock.readLock(); |
| 440 | + this.writeLock = lock.writeLock(); |
432 | 441 | }
|
433 | 442 |
|
434 | 443 | 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; |
442 | 449 | }
|
443 |
| - else if (this.queue.remove(key)) { |
| 450 | + |
| 451 | + try { |
| 452 | + this.readLock.lock(); |
444 | 453 | 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(); |
446 | 459 | }
|
447 | 460 | }
|
448 |
| - finally { |
449 |
| - this.lock.readLock().unlock(); |
450 |
| - } |
451 |
| - this.lock.writeLock().lock(); |
| 461 | + |
| 462 | + this.writeLock.lock(); |
452 | 463 | try {
|
453 | 464 | // retrying in case of concurrent reads on the same key
|
454 |
| - if (this.queue.remove(key)) { |
| 465 | + if ((cached = this.cache.get(key)) != null) { |
455 | 466 | this.queue.add(key);
|
456 |
| - return this.cache.get(key); |
| 467 | + this.queue.remove(key); |
| 468 | + return cached; |
457 | 469 | }
|
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) { |
459 | 476 | K leastUsed = this.queue.poll();
|
460 | 477 | if (leastUsed != null) {
|
461 | 478 | this.cache.remove(leastUsed);
|
| 479 | + cacheSize--; |
462 | 480 | }
|
463 | 481 | }
|
464 |
| - V value = this.generator.apply(key); |
| 482 | + |
465 | 483 | this.queue.add(key);
|
466 | 484 | this.cache.put(key, value);
|
| 485 | + this.size = cacheSize + 1; |
| 486 | + |
467 | 487 | return value;
|
468 | 488 | }
|
469 | 489 | finally {
|
470 |
| - this.lock.writeLock().unlock(); |
| 490 | + this.writeLock.unlock(); |
471 | 491 | }
|
472 | 492 | }
|
473 | 493 | }
|
|
0 commit comments