防止重复提交
LRUMap
LRU 是 Least Recently Used 的缩写,即最近最少使用,是一种常用的数据淘汰算法,选择最近最久未使用的数据予以淘汰。
1 <!-- 集合工具类 apache commons collections --> 2 <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 --> 3 <dependency> 4 <groupId>org.apache.commons</groupId> 5 <artifactId>commons-collections4</artifactId> 6 <version>4.4</version> 7 </dependency> 8
1 import org.apache.commons.collections4.map.LRUMap; 2 3 /** 4 * 幂等性判断 5 */ 6 public class IdempotentUtils { 7 8 // 根据 LRU(Least Recently Used,最近最少使用)算法淘汰数据的 Map 集合,最大容量 100 个 9 private static LRUMap<String, Integer> reqCache = new LRUMap<>(100); 10 11 /** 12 * 幂等性判断 13 * @return 14 */ 15 public static boolean judge(String id, Object lockClass) { 16 synchronized (lockClass) { 17 // 重复请求判断 18 if (reqCache.containsKey(id)) { 19 // 重复请求 20 System.out.println("请勿重复提交!!!" + id); 21 return false; 22 } 23 // 非重复请求,存储请求 ID 24 reqCache.put(id, 1); 25 } 26 return true; 27 } 28 } 29
扩展知识——LRUMap 实现原理分析
LRUMap 的本质是持有头结点的环回双链表结构,它的存储结构如下:
1 AbstractLinkedMap.LinkEntry entry;
当调用查询方法时,会将使用的元素放在双链表 header 的前一个位置,源码如下:
1 public V get(Object key, boolean updateToMRU) { 2 LinkEntry<K, V> entry = this.getEntry(key); 3 if (entry == null) { 4 return null; 5 } else { 6 if (updateToMRU) { 7 this.moveToMRU(entry); 8 } 9 10 return entry.getValue(); 11 } 12 } 13 protected void moveToMRU(LinkEntry<K, V> entry) { 14 if (entry.after != this.header) { 15 ++this.modCount; 16 if (entry.before == null) { 17 throw new IllegalStateException("Entry.before is null. This should not occur if your keys are immutable, and you have used synchronization properly."); 18 } 19 20 entry.before.after = entry.after; 21 entry.after.before = entry.before; 22 entry.after = this.header; 23 entry.before = this.header.before; 24 this.header.before.after = entry; 25 this.header.before = entry; 26 } else if (entry == this.header) { 27 throw new IllegalStateException("Can't move header to MRU This should not occur if your keys are immutable, and you have used synchronization properly."); 28 } 29 30 } 31
如果新增元素时,容量满了就会移除 header 的后一个元素,添加源码如下:
1 protected void addMapping(int hashIndex, int hashCode, K key, V value) { 2 // 判断容器是否已满 3 if (this.isFull()) { 4 LinkEntry<K, V> reuse = this.header.after; 5 boolean removeLRUEntry = false; 6 if (!this.scanUntilRemovable) { 7 removeLRUEntry = this.removeLRU(reuse); 8 } else { 9 while(reuse != this.header && reuse != null) { 10 if (this.removeLRU(reuse)) { 11 removeLRUEntry = true; 12 break; 13 } 14 reuse = reuse.after; 15 } 16 if (reuse == null) { 17 throw new IllegalStateException("Entry.after=null, header.after=" + this.header.after + " header.before=" + this.header.before + " key=" + key + " value=" + value + " size=" + this.size + " maxSize=" + this.maxSize + " This should not occur if your keys are immutable, and you have used synchronization properly."); 18 } 19 } 20 if (removeLRUEntry) { 21 if (reuse == null) { 22 throw new IllegalStateException("reuse=null, header.after=" + this.header.after + " header.before=" + this.header.before + " key=" + key + " value=" + value + " size=" + this.size + " maxSize=" + this.maxSize + " This should not occur if your keys are immutable, and you have used synchronization properly."); 23 } 24 this.reuseMapping(reuse, hashIndex, hashCode, key, value); 25 } else { 26 super.addMapping(hashIndex, hashCode, key, value); 27 } 28 } else { 29 super.addMapping(hashIndex, hashCode, key, value); 30 } 31 }
判断容量的源码:
1 public boolean isFull() { 2 return size >= maxSize; 3 }
** 容量未满就直接添加数据:
1 super.addMapping(hashIndex, hashCode, key, value);
如果容量满了,就调用 reuseMapping 方法使用 LRU 算法对数据进行清除。
综合来说:LRUMap 的本质是持有头结点的环回双链表结构,当使用元素时,就将该元素放在双链表 header 的前一个位置,在新增元素时,如果容量满了就会移除 header 的后一个元素。

浙公网安备 33010602011771号