Mybatis基础支持层-缓存模块

一级缓存/二级缓存/装饰器模式

Posted by Claire on July 4, 2020

mybatis基础支持层-缓存模块

一、装饰器模式

  • 使用场景:需要动态的为对象添加功能,基于组合的方式实现(而不是继承,组合优于继承),保证已有组件的稳定性
  • 角色:
    • Component 组件: 接口,定义了全部组件实现类以及所有装饰器实现的行为
    • ConcreteComponent 具体组件实现类 : 实现了Component接口,实现基础功能的实现,高级功能是通过装饰器方式添加的
    • Decorator 装饰器:所有装饰器的父类,是一个实现了component接口的抽象类,其中封装了Component对象,也是被装饰的对象。而这个被装饰者只需要Component对象即可,实现了装饰器的组合和复用
    • ConcreteDecorator: 具体的装饰器实现类,主要是为Component对象添加功能
  • 例子:FileInputStream 没有缓冲的功能,然后BufferedInputStream就是提供缓冲功能的装饰器,每次read的时候从文件中预获取一部分数据到缓冲区,可以减少用户态和内核态的切换,提高性能
  • mybatis例子: mybatis中的装饰器,将Decoretor和Component接口合为一体
  • 装饰器的优点:装饰器采用组合的形式,更为灵活,可扩展性强,能够创建不同的组件满足不同的需求,保障了基础组件的稳定性,符合“开放-封闭”原则

二、Cache接口

  • Cache是顶级接口,涵盖getId、putObject、getObject、removeObject、getSize、getReadWriteLock,比较重要的就是put get remove的方法
  • PerpetualCache 实现Cache的接口,使用HashMap实现缓存,使用map的put get remove来Cache中的方法,担当ConcreteComponent角色
  • 对于其他装饰类,主要是丰富PerpetualCache类的功能

  • BlockingCache 内含Cache接口 阻塞版本的装饰器,保证只有一个线程到数据库中查找指定key的数据
  • BlockingCache 获取缓存数据
 @Override
  public Object getObject(Object key) {
    //尝试获取锁
    acquireLock(key);
    //获取锁成功
    Object value = delegate.getObject(key);
    //获取内容
    if (value != null) {
      //释放锁
      releaseLock(key);
    }
    return value;
  }
    private void acquireLock(Object key) {
    Lock lock = getLockForKey(key);
    if (timeout > 0) {
      //加锁超时
      try {
        //带超时版本的加锁
        boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
        if (!acquired) {
          throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());
        }
      } catch (InterruptedException e) {
        throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
      }
    } else {
      //直接加锁
      lock.lock();
    }
  }

    private ReentrantLock getLockForKey(Object key) {
    //直接获取锁,如果还没有加锁就生成
    return locks.computeIfAbsent(key, k -> new ReentrantLock());
  }
  • BlockingCache 添加缓存数据

  @Override
  public void putObject(Object key, Object value) {
    try {
      delegate.putObject(key, value);
    } finally {
      releaseLock(key);
    }
  }
  • FifoCache 先进先出Deque修饰keys的缓存装饰器,添加缓存的时候,如果缓存数量大设置,则会删除最早的缓存。get yu remove的方法直接调用Cache的get和remove方法
 @Override
  public void putObject(Object key, Object value) {
    cycleKeyList(key);
    delegate.putObject(key, value);
  }

  private void cycleKeyList(Object key) {
    //添加key
    keyList.addLast(key);
    //key数量大于设置
    if (keyList.size() > size) {
      //删除第一个缓存
      Object oldestKey = keyList.removeFirst();
      delegate.removeObject(oldestKey);
    }
  }

  • LruCache 最近最少使用修饰keys的缓存装饰器,清理缓存的时候它会清除最近最少使用的项。使用new LinkedHashMap<Object, Object>(size, .75F, true)的基本原理来实现最近最少使用的语义。get和put方法会做少许修饰操作,remove方法直接使用Cache的remove
// 初始化的时候初始化Map
 public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);
  }
   public void setSize(final int size) {
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  } 

//添加put
  @Override
  public void putObject(Object key, Object value) {
    //放入缓存
    delegate.putObject(key, value);
    // 对放入的缓存做特殊处理
    cycleKeyList(key);
  }

 private void cycleKeyList(Object key) {
    //放入LRU MAP
    keyMap.put(key, key);

    //如果存在需要删除的key,就清除
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

//获取值
    @Override
  public Object getObject(Object key) {
    //LRU MAP 刷新使用 ,修改次序
    keyMap.get(key); // touch
    //Cache 获取值
    return delegate.getObject(key);
  }

三、SoftCache & WeakCache 主要通过软引用、弱引用实现

强引用、软引用、引用队列、弱引用、幽灵引用(虚引用)

1.强引用

  • 场景: 通常使用的 P p = new P();
  • 采用强引用,即使JVM内存空间不足,GC也不会回收该对象。因而当出现JVM内存不足情况,就会抛 OOM

2.软引用 SoftReference

  • 使用SoftReference修饰,强度低于强引用
  • 场景:当JVM内存不足时,会回收软引用对象,如果回收了软引用对象还是内存不足,才会抛出OOM。因而,软引用适用于可以通过恢复来还原的对象,比如一些临时缓存,可以通过数据库重新加载的,可以使用软引用来修饰
  • 软引用的对象,需要通过Reference.get()的方法来确认对象是否被回收

3.引用队列 ReferenceQueue

  • 场景: 用于对象可达性发生变化,引用队列就是用于收集这些信息的队列。例如SoftRerence 可以匹配一个ReferenceQueue, 当对象值被JVM回收后,还是能够读取ReferenceQueue获取对象信息

4.弱引用

  • WeakReference修饰,比软引用强度弱
  • 当JVM进行GC垃圾回收的时候,如果对象是弱引用修饰的,就会被回收掉,因而它的生命周期尽可能在两次JVM GC之间
  • 典型case: java.util.WeakHashMap

5.幽灵引用(虚引用)

  • PhantomReference修饰,最弱的引用类型
  • 可以用于实现比较精细的内存控制
  • 幽灵引用,必须指定一个引用队列,当GC准备回收一个对象,而对象存在幽灵引用的时候,会将该虚引用加入到与之关联的引用队列中

6.SoftCache

用到上面学习的ReferenceQueue

  • 属性
//最近使用的缓存项,添加到 hardLinksToAvoidGarbageCollection ,能够避免被GC回收
  private final Deque<Object> hardLinksToAvoidGarbageCollection;
  //ReferenceQueue ,引用队列,用于记录已经被GC回收的SoftEntry对象
  private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
  private final Cache delegate;
    //默认禁止GC回收的缓存项数量,默认256
  private int numberOfHardLinks;
  • putObject:主要关注一个无效key的清除,以及软引用绑定的引用队列的应用
  @Override
  public void putObject(Object key, Object value) {
    //操作之前,都会清除一下value已被GC回收的引用
    removeGarbageCollectedItems();
    //放入缓存,value是被SoftEntry修饰的
    delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
  }

   private void removeGarbageCollectedItems() {
    SoftEntry sv;
    //从value绑定的 ReferenceQueue中查找(如果value被回收,会将对象放在ReferenceQueue中)
    while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
      //如果value被回收,就删除对应的key
      delegate.removeObject(sv.key);
    }
  }


  //主要继承SoftReference,软引用的特性,指定了一个ReferenceQueue
  private static class SoftEntry extends SoftReference<Object> {
    private final Object key;

    SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
      super(value, garbageCollectionQueue);
      this.key = key;
    }
  }
  • getObject
@Override
  public Object getObject(Object key) {
    Object result = null;
    @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
      //直接从Cache获取值
    SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
    if (softReference != null) {
      //如果软引用存在
      result = softReference.get();

      if (result == null) {
        //获取值为空,已被GC回收,清除对应key
        delegate.removeObject(key);
      } else {
        //未被GC回收
        // See #586 (and #335) modifications need more than a read lock
        synchronized (hardLinksToAvoidGarbageCollection) {
          //同步对象,添加结果,最近使用,不允许GC回收
          hardLinksToAvoidGarbageCollection.addFirst(result);
          //如果超过数量的限制
          if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
            //清除最老的缓存项
            hardLinksToAvoidGarbageCollection.removeLast();
          }
        }
      }
    }
    return result;
  }
  • removeObject 比较简单,就是清除无效Key,并删除对应Key
  @Override
  public Object removeObject(Object key) {
    removeGarbageCollectedItems();
    return delegate.removeObject(key);
  }
  • clear
  @Override
  public void clear() {
    synchronized (hardLinksToAvoidGarbageCollection) {
      hardLinksToAvoidGarbageCollection.clear();
    }
    removeGarbageCollectedItems();
    delegate.clear();
  }

7. WeakCache

  • 方法流程与SoftCache, 使用WeakEntry 替代SoftEntry ,使用 WeakReference 替换 SoftReference

8.ScheduledCache

周期性清理的装饰器

  • 属性
  private final Cache delegate;
  //清理的周期
  protected long clearInterval;
  //最近一次清理的时间
  protected long lastClear;
  • putObject/getObject/removeObject 方法前都会调用clearWhenStale方法,如果满足清理时间天津,就清理Cache

9.LoggingCache

内含一个Log对象,用于记录getObject时候的日志

10.SynchronizedCache

针对Cache接口中的方法,都使用synchronized修饰,进行同步控制

11.SerializedCache

putObject 对value进行序列化,getObject 对value进行反序列化

四、CacheKey

  • CacheKey是Cache中唯一标识一个Cache的标志,内含多项影响因素
  • 内部主要用于确定两个Cache是否相同,提供update,updateAll,hashcode,equals,tostring,clone等方法