防止重复提交

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 的后一个元素。




posted @ 2021-11-24 17:47  蹉~跎  阅读(49)  评论(0)    收藏  举报