用HashMap和双链表实现多线程下的LRU缓存算法(java版)
来源:互联网 发布:c语言考研题库 编辑:程序博客网 时间:2024/06/04 19:36
缓存的目的:缓存主要为了保存数据的,在项目中,开启服务器的时候,将大量被访问的数据从数据库中查到,放入到缓存中,服务器开启后,用户从前端向后台发送请求,直接从缓存中去取,不用查数据库,加快数据的访问。
我的缓存的需求:主要想保存ArticleBean(中有很多属性),加入到缓存的时候按照点击量的降序,定时更新缓存的时候能将按照点击量的降序加入到合适的位置,而查找文章的时候需要根据文章的id直接缓存中去获取,不需要遍历。
思路:
链表:插入到合适的位置,不需要数据的移动
HashMap:直接根据文章的id获取ArticleBean对象。
假设:
如果将数据分别保存到链表和HashMap中,那么数据保存了两份,占用了两份内存,在内存宝贵的情况下这样是不行的。
所以:
将ArticleBean转化为一个节点Entry,在节点中保存ArticleBean作为value、前一个节点,后一个节点,key作为键值。
好处:
1.保存在HashMap可以直接定位节点的位置
2.用节点连接起来又可以直接插入节点
3.每个节点在缓存中只有这一份
多线程:
用读写锁来实现多线程下的安全,当增加、移动、删除的时候用写锁,而查阅的时候用读锁。
我的代码:
package com.zhangyike.lru;import java.util.HashMap;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;public class MyLRU<K,V> { private int cacheSize;//缓存的长度 private HashMap<K, Entry<K,V>> nodes;//缓存容器,node的建其实就是Entry的键 private int currentSize;//缓存中的元素长度 private Entry<K,V> head;//链表的头部 private Entry<K,V> tail;//链表的尾部 /* * 该类对象,保证该类对象在服务器上只有一份。 */ private static MyLRU instance; private static ReadWriteLock rwl = new ReentrantReadWriteLock();//用于修改缓存中数据的读写锁 private static ReadWriteLock rwlInstance = new ReentrantReadWriteLock();//用于创建对象的读写锁 /* * 虽然getLruThread(..)方法中加了rwl.writeLock().lock();,为了以防万一,该方法中也要加锁。 * 这样不会造成死锁,属于可重入锁,这两个锁的对象是相同的,就不会重新申请锁了。 */ private MyLRU(int cacheSize){ rwlInstance.writeLock().lock(); try{ this.cacheSize = cacheSize; currentSize = 0; nodes = new HashMap<K,Entry<K,V>>(cacheSize);//根据提供的参数初始化容器初始化容器 }finally{ rwlInstance.writeLock().unlock(); } } public static MyLRU getLruThread(){ return getLruThread(16); } public static MyLRU getLruThread(int cacheSize){ //多个线程可以同时去读这个对象, rwl.readLock().lock();; try{ /* * 如果这个对象是空的,那么就关闭这个read锁,打开写锁,去获取这个对象 */ if(instance == null){ rwl.readLock().unlock();//关闭读锁 rwlInstance.writeLock().lock();;//打开写锁 /* * 再次判断instance对象是否为空 * 在多线程情况下,当多个线程同时读数据,发现instance是null,则read锁关闭,写锁打开,只有一个线程进来了, * 获取到对象后,写锁关闭,等待在外面的线程就依次获取到这个写锁,进来,如果不做判断,还会重新获取数据,这样显然没有必要。 * 所以必须要加这个判断。 */ if(instance == null){ //获取instance对象 instance = new MyLRU(cacheSize); } /* * 这两行的顺序无所谓,因为写锁中可以去读数据,写锁锁住的情况下说明只有当前线程持有该锁, * 其他线程不能去读,也不能去写,读数据不会引起并发,所以可以写中读。 * * 但是读锁中不能打开写锁,如果读锁中有写锁,那么写锁会修改数据,而此时多个线程在同时读数据,那么就会出现并发问题 */ rwl.readLock().lock();//打开读锁 rwlInstance.writeLock().unlock();;//关闭写锁 return instance; } }finally{ rwl.readLock().unlock();//关闭读锁 } return instance; } public Entry<K, V> get(K key){ rwl.readLock().lock(); try{ Entry<K,V> node = nodes.get(key); /* * 为什么得到了这个节点,就把这个节点移动到链表的头部呢? */ if (node != null) { rwl.readLock().unlock(); rwl.writeLock().lock(); //打开写锁的目的就是将获取到的节点移动到链表的头部,如果节点就在链表的头部,那么就没必要移动了。 if (node != head) { moveToHead(node); } rwl.writeLock().unlock(); rwl.readLock().lock(); return node; }else{ return null; } }finally{ rwl.readLock().unlock(); } } //将内容添加到节点的最头部 public void put(K key, V value){ rwl.writeLock().lock(); try{ Entry<K, V> nd = nodes.get(key); if (nd == null) { //判断缓存容器的大小 //容器空的时候 if (currentSize == 0) { nd = new Entry<K,V>(tail,null,key,value); head = nd;//让该节点成为头节点 tail = nd;//让该节点成为尾节点 currentSize++; }else if (cacheSize == currentSize) {//容器满的时候 //因为尾节点的点击量最小,所以要将尾节点从链表中移除 nodes.remove(tail.key);//HashMap中移除链表的尾节点 removeLast();//移除最后一个 nd = new Entry<K,V>(tail,null,key,value); }else{ //实际长度加1 currentSize++; nd = new Entry<K,V>(tail,null,key,value); } }else{ nd.value = value;//覆盖节点中的值 } //将节点移动到缓存链的最前面 moveToHead(nd); //将节点加入到缓存中 nodes.put(key, nd); }finally{ rwl.writeLock().unlock(); } } //根据key值删除数据,该数据只在链满的时候才删除。 //删除链表中的数据,只用将链表前后两个节点连接就好 public void remove(K key){ rwl.writeLock().lock(); try{ Entry<K, V> node = nodes.get(key); if (node != null) { if (node == head) { head.next.pre = null; head = node.next; } if (node == tail) { tail.pre.next = null; tail = tail.pre; } if (node.pre != null) { node.pre.next = node.next; } if (node.next != null) { node.next.pre = node.pre; } node = null; } nodes.remove(key);//删除hashtable中的链 }finally{ rwl.writeLock().unlock(); } } //移除双向链表的尾节点 private void removeLast() { rwl.writeLock().lock(); try{ if (tail != null) { //判断链表是不是只有一个节点 if (tail.pre == null) { head = null; }else{ tail.pre.next = null; } tail = tail.pre; } }finally{ rwl.writeLock().unlock(); } } //输出双链中的内容 public void sop(){ rwl.readLock().lock(); try{ for (Entry<K, V> node = head; node != null; node = node.next) { System.out.println("[" + node.key + " = " + node.value + "]"); } }finally{ rwl.readLock().unlock(); } } //将节点移动到最前面 private void moveToHead(Entry<K, V> node) { rwl.writeLock().lock(); try{ //node节点就是头结点 if(node == head){ return; } //node节点是最后一个节点 if (node == tail) { //让node的前一个节点的next指向null,并让前一个节点变为tail。 node.pre.next = null; tail = node.pre; } //node节点前面有元素 if (node.pre != null) { //更改node的前一个节点的next的指向 node.pre.next = node.next; } //node前面有元素 if (node.next != null) { //更改node的下一个节点的pre的指向 node.next.pre = node.pre; } //将node节点变为头结点 if (null != head) { node.next = head; head.pre = node; } node.pre = null; head = node; //只有一个节点 if (tail == null) { tail = node; } }finally{ rwl.writeLock().unlock(); } } public int size(){ rwl.readLock().lock(); try{ return currentSize; }finally{ rwl.readLock().unlock(); } } public void clear(){ rwl.writeLock().lock(); try{ if (head != tail) { Entry<K, V> node = head; while (node != null) { node.value = null; node.pre = null; node = node.next; } currentSize = 0; } }finally{ rwl.writeLock().unlock(); } } public class Entry<K,V>{ Entry<K, V> pre; Entry<K,V> next; K key; V value; Entry(Entry<K, V> p,Entry<K,V> next,K k,V value){ this.pre = p; this.next = next; this.key = k; this.value = value; } }}
Demo测试:
package com.zhangyike.lru;public class LRUDemo { public static void main(String[] args) { MyLRU<Integer, String> lru = MyLRU.getLruThread(10); //添加100个数据,因为缓存空间是10,所以只要最后10个 for (int i = 0; i < 100; i++) { lru.put(i, i + "--" + i); } System.out.println("第一次添加缓存的结果为:"); lru.sop(); lru.put(91, 91+"**");//将91-91用91**替代,并且移动到最前方 lru.remove(93);//将93移走 System.out.println(); System.out.println("更改后缓存的结果为:"); lru.sop(); }}
测试结果:
第一次添加缓存的结果为:
[99 = 99–99]
[98 = 98–98]
[97 = 97–97]
[96 = 96–96]
[95 = 95–95]
[94 = 94–94]
[93 = 93–93]
[92 = 92–92]
[91 = 91–91]
[90 = 90–90]
更改后缓存的结果为:
[91 = 91**]
[99 = 99–99]
[98 = 98–98]
[97 = 97–97]
[96 = 96–96]
[95 = 95–95]
[94 = 94–94]
[92 = 92–92]
[90 = 90–90]
- 用HashMap和双链表实现多线程下的LRU缓存算法(java版)
- LRU缓存算法Java实现
- LRU缓存的java实现
- 缓存算法及LRU的实现(Java)
- 常见缓存算法和LRU的c++实现
- 常见缓存算法和LRU的c++实现
- 常见缓存算法和LRU的c++实现
- 常见缓存算法和LRU的c++实现
- 常见缓存算法和LRU的c++实现
- 常见缓存算法和LRU的C++实现
- Java 自定义实现 LRU 缓存算法
- Java 自定义实现 LRU 缓存算法
- LRU缓存算法实现
- 哈希链表实现的LRU缓存算法
- LRU缓存的实现算法讨论
- 基于LRU算法的缓存实现
- LinkedList实现基于LRU算法的缓存
- LRU缓存介绍和JAVA实现
- SQL Server Browser 服务
- 两道面试算法题
- 微信小程序开发中常见问题及解决方法
- 一次net::err_connection_reset的过程记录
- 字符串扩展
- 用HashMap和双链表实现多线程下的LRU缓存算法(java版)
- 深度学习原理与应用笔记
- 二分查找
- 边框,背景属性
- 【cocos2d-js官方文档】十七、事件分发机制
- 输出某段区间内的所有素数
- Deep LSTM siamese network for text similarity源码分析
- DAY4学习笔记
- 安卓实现上传文件到服务器(转)