Java 中 LRU 缓存机制详解与实践
存技术是提升系统性能的重要手段,其中 LRU(Least Recently Used,最近最少使用)缓存策略凭借其简单高效的特点,被广泛应用于各种场景。
一、LRU 缓存策略原理
LRU 缓存策略的核心思想是:当缓存容量已满,需要添加新元素时,移除最近最少使用的元素,为新元素腾出空间。这种策略基于一个常见的假设:近期使用过的数据,在未来被再次使用的概率较高;而长时间未使用的数据,在未来被使用的可能性较低。
二、Java 中 LRU 缓存的基础实现
我们可以利用LinkedHashMap的特性可以快速实现一个 LRU 缓存。LinkedHashMap支持按访问顺序维护元素顺序,通过将其构造函数的accessOrder参数设为true,即可实现按访问顺序排序,实现简单的LRU机制。
import java.util.LinkedHashMap;
import java.util.Map;public class LRUCache extends LinkedHashMap<Integer, Integer> {private int capacity;public LRUCache(int capacity) {// loadFactor 设置为 0.75f,accessOrder = true 表示按照访问顺序排序super(capacity, 0.75f, true);this.capacity = capacity;}// 重写 removeEldestEntry 方法,当缓存超过容量时移除最老的条目@Overrideprotected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {return size() > capacity;}
}
上述代码定义了一个LRUCache类,继承自LinkedHashMap。在构造函数中,通过super(capacity, 0.75f, true)设置了LinkedHashMap的容量、负载因子,并开启按访问顺序排序。重写的removeEldestEntry方法用于判断缓存是否超过容量,若超过则返回true,触发移除最久未使用元素的操作。这种实现方式简单直观,get和put操作的时间复杂度均为 O (1),非常适合对性能要求不高、追求代码简洁性的场景,如快速原型开发或轻量级缓存需求。
三、LRU 缓存的线程安全实现方案
前面LRU 缓存实现可能会出现数据不一致等问题,因此需要采取措施保证线程安全。
1. 使用Collections.synchronizedMap()包裹LinkedHashMap
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;public class ThreadSafeLRUCache extends LinkedHashMap<Integer, Integer> {private final int capacity;public ThreadSafeLRUCache(int capacity) {super(capacity, 0.75f, true);this.capacity = capacity;// 使用 synchronizedMap 实现线程安全}@Overrideprotected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {return size() > capacity;}// 返回线程安全的 Mappublic static Map<Integer, Integer> createThreadSafeLRU(int capacity) {return Collections.synchronizedMap(new ThreadSafeLRUCache(capacity));}
}
这种方式通过Collections.synchronizedMap对LinkedHashMap进行包裹,实现线程安全。但它是对整个 Map 加锁,性能较差,且所有方法调用都需要外部同步。
2. 手动加锁(推荐)
import java.util.LinkedHashMap;
import java.util.Map;public class ThreadSafeLRUCache {private final Map<Integer, Integer> cache;private final int capacity;private final Object lock = new Object();public ThreadSafeLRUCache(int capacity) {this.capacity = capacity;this.cache = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {@Overrideprotected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {return size() > capacity;}};}public void put(Integer key, Integer value) {synchronized (lock) {cache.put(key, value);}}public Integer get(Integer key) {synchronized (lock) {return cache.get(key);}}public Integer remove(Integer key) {synchronized (lock) {return cache.remove(key);}}
}
手动使用ReentrantLock或synchronized关键字控制并发访问,相比synchronizedMap,其控制粒度更细,可以按需优化,更加灵活,适用于需要更精细控制的场景。
3. 使用ConcurrentHashMap + 自定义双向链表
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;public class HighPerformanceLRUCache<K, V> {private final int capacity;private final ConcurrentHashMap<K, V> map;private final ConcurrentLinkedQueue<K> queue;public HighPerformanceLRUCache(int capacity) {this.capacity = capacity;this.map = new ConcurrentHashMap<>(capacity);this.queue = new ConcurrentLinkedQueue<>();}public V get(K key) {return map.get(key);}public void put(K key, V value) {if (map.containsKey(key)) {map.put(key, value);return;}if (map.size() >= capacity) {K eldest = queue.poll();if (eldest != null) {map.remove(eldest);}}map.put(key, value);queue.offer(key);}public void remove(K key) {map.remove(key);queue.remove(key);}
}
使用ConcurrentHashMap和ConcurrentLinkedQueue,在高并发下具有较好的性能和可扩展性。不过,该版本为简化版,不完全等价于标准 LRU,若需完整 LRU 行为,应结合ConcurrentSkipListMap或自定义线程安全双向链表实现,适合高并发需求场景。
4. 使用第三方库(如 Caffeine、Ehcache)
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Cache;public class LRUCacheExample {public static void main(String[] args) {Cache<Integer, Integer> cache = Caffeine.newBuilder().maximumSize(100).build();cache.put(1, 1);System.out.println(cache.getIfPresent(1)); // 输出 1}
}
在生产环境中,推荐使用成熟的缓存库,如 Caffeine、Ehcache 等。这些库不仅实现了高性能、线程安全,还支持多种淘汰策略(大小、时间、引用类型等)。
四、总结
不同的 LRU 缓存实现方案各有优劣,在实际开发中,我们需要根据具体需求选择合适的方案:
- synchronizedMap:线程安全,性能低,适用于快速原型开发。
- 手动加锁:线程安全,性能中等,适合对控制粒度有较高要求的场景。
- 自定义结构(Concurrent):线程安全,性能高,适用于高并发需求场景。
- 第三方库(如 Caffeine):线程安全,性能高,支持丰富功能,推荐用于生产环境。