使用软引用构建敏感数据的缓存
一、实现原理
1.应用场景
查询频率较高的数据;每次查询均需要通过接口与数据库交互,构建对象仍需要占用一部分内存;即便上次查询的结果仍在内存中还未被GC回收但仍需要再次进行相同的查询操作;
将查询结果放入内存--大量占用内存空间,增加发生内存溢出的可能;
每次都重新查询--当前的查询结果使用完毕后,实现的缺陷在于即使垃圾收集线程还没有进行垃圾收集,包含雇员档案信息的对象仍然完好地保存在内存中,应用程序也要重新构建一个对象
折中的方法,能重新获取那些尚未被回收的Java对象的引用,必将减少不必要的访问,大大提高程序的运行速度
2.软引用的特点
对于软引用关联着的对象,如果内存充足,则垃圾回收器不会回收该对象,如果内存不够了,就会回收这些对象的内存
3.软引用配合引用队列
软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
二、代码实现
实现思想
- 工具类使用懒汉模式的单例实现,避免多线程调用时出现问题,对外接口使用synchronized 关键字修饰
- 构建缓存,使用Hashtable,key:存储内容的唯一标识,value:存储对象的软引用实现;取元素时,若此缓存中存在,则说明此时对象未被回收、或已被回收,但软引用在引用队列中未被清除;若无,说明存储的对象已被GC;
- 构建软引用队列,泛型 T 为要存储的对象,当软引用所依赖的对象被GC回收后,JVM将此软引用加入到与之关联的引用队列中。即,此时在等待GC到达 out of memory 前回收此时占用的内存,故每次放入新的对象前,先判断此队列是否有值,若有,主动释放所占用的内存空间
/** * 员工信息类 */ public class Employee { private String id ; // 主键 private String name ; // 姓名 private String department ; // 部门 private Double salary ; // 工资 public Employee(String id){ this.id = id ; } public Employee(String id, String name, String department, Double salary) { super(); this.id = id; this.name = name; this.department = department; this.salary = salary; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } @Override public String toString() { return "Employee [id=" + id + ", name=" + name + ", department=" + department + ", salary=" + salary + "]"; } }
import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.Hashtable; /** * 员工信息缓存 */ public class EmployeeCache { // 单例模式:懒汉式 private static EmployeeCache employeeCache ; private EmployeeCache(){ // 构造缓存对象时,初始化缓存容器、软索引队列 cache = new Hashtable<String, EmployeeCache.EmployeeRef>(); queue = new ReferenceQueue<Employee>(); } public synchronized static EmployeeCache getInstance(){ if(null == employeeCache){ employeeCache = new EmployeeCache(); } return employeeCache; } // 缓存容器 private static Hashtable<String,EmployeeRef> cache = null; // 软索引队列 private static ReferenceQueue<Employee> queue = null ; /** * 私有内部类:Employee 的软索引对象 */ private class EmployeeRef extends SoftReference<Employee>{ private String uniqueKey = ""; public EmployeeRef(Employee referent, ReferenceQueue<? super Employee> q) { super(referent, q); this.setUniqueKey(referent.getId()); } public void setUniqueKey(String uniqueKey) { this.uniqueKey = uniqueKey; } } /** * 向缓存中添加元素 */ public void put(Employee employee){ // 清空已在引用队列中的软索引对象,释放空间 clearReferenceQueue(); EmployeeRef ref = new EmployeeRef(employee, queue); cache.put(ref.uniqueKey, ref); } private void clearReferenceQueue(){ EmployeeRef ref = null ; // 引用队列中的数据出队列 while((ref = (EmployeeRef)queue.poll()) != null){ // 同时清除该软引用作为KEY的对象内容 cache.remove(ref.uniqueKey); } } /** * 从缓存中取出元素 * @param key * @return */ public Employee get(String key){ Employee employee = null ; if(cache.containsKey(key)){ EmployeeRef employeeRef = cache.get(key); employee = employeeRef.get(); } if(null == employee){ employee = new Employee(key); EmployeeRef ref = new EmployeeRef(employee, queue); cache.put(key, ref); } return employee ; } }
public class EmployeeCacheMain { private static EmployeeCache cache = EmployeeCache.getInstance(); /** * @param args */ public static void main(String[] args) { Employee e1 = new Employee("1", "张三", "测试部门", 10000.0); Employee e2 = new Employee("2", "李四", "开发部门", 15000.0); cache.put(e1); cache.put(e2); Employee employee = cache.get("1"); System.out.println(employee.toString()); e2.setDepartment("测试部门"); cache.put(e2); System.out.println(cache.get("2").getDepartment()); } }
博文参考:
java引用类型
浅谈java对象引用及对象赋值