mybatis底层组件介绍-缓存

缓存

mybatis可以对查询的结果进行缓存,而缓存功能是由Cache及其实现类来提供的

Cache

public interface Cache {

  // 缓存的唯一标志
  String getId();

  // 向缓存中添加缓存项
  void putObject(Object key, Object value);

  // 获取指定的缓存项
  Object getObject(Object key);

  // 移除指定的缓存项
  Object removeObject(Object key);

  // 清除整个缓存
  void clear();

  // 当前已经存储的缓存项的个数
  int getSize();

  // 获取读写锁,比如多个线程同时修改同一个缓存项的时候,就可以使用读写锁
  default ReadWriteLock getReadWriteLock() {
    return null;
  }
}

众多实现类

Cache使用了装饰者的设计模式,最基本的功能由PerpetualCache来提供,其他的实现类通过装饰者的设计模式,在PerpetualCache提供功能的基础上进行增强

PerpetualCache

PerpetualCache的整体实现比较简单,主要使用一个Map来作为缓存,存储缓存项

public class PerpetualCache implements Cache {

  private final String id;

  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

接下来看几个比较常用的实现类

BlockingCache

当一个线程访问指定的key时,如果当前缓存中不存在,那么该线程会在该key上设置一个锁,接下来访问相同key的线程会阻塞在这个锁上,直到调用了putObject向缓存中添加了该key
这里进行增强的目的是为了避免缓存击穿,当多个线程同时访问缓存中的同一个key时,如果此时缓存中不存在对应key的缓存项,那么这些线程就会并发地去数据库中查询记录,从而给数据库带来了压力,通过使用这个增强,就可以确保只有一个线程去访问数据库,从而减少数据库的压力

重要属性
private final Cache delegate;
// 用来存储每个key上的锁
private final ConcurrentHashMap<Object, CountDownLatch> locks;	
重要方法
getObject
public Object getObject(Object key) {
  acquireLock(key);
  Object value = delegate.getObject(key);
  if (value != null) {
    releaseLock(key);
  }
  return value;
}
private void acquireLock(Object key) {
  CountDownLatch newLatch = new CountDownLatch(1);
  while (true) {
    // 只有当前的缓存中不存在指定的key时,才会进行put操作
    CountDownLatch latch = locks.putIfAbsent(key, newLatch);
	// 这里为null一共有两种情况
	// (1)第一次访问缓存中的某个key
	// (2)调用了releaseLock,该方法中会从locks中移除指定的key,并且唤醒其他等待线程,在这种情况下,会从多个等待线程中选出一个线程新创建的CountDownLatch作为新的锁,其他被唤醒的等待线程会在这个新的锁上进行等待,也就是说如果有多个等待线程,每个时刻只能有一个线程跳出循环,执行下面的流程,其他的线程只能等待这个线程释放锁,重新进行新一轮的竞争
    if (latch == null) {
      break;
    }
    try {
    	// 根据是否设置了timeout,来判断是执行有限时间等待还是无限时间等待
      if (timeout > 0) {
        boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS);
        if (!acquired) {
          throw new CacheException(
              "Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
        }
      } else {
        latch.await();
      }
    } catch (InterruptedException e) {
      throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
    }
  }
}
putObject
public void putObject(Object key, Object value) {
  try {
    delegate.putObject(key, value);
  } finally {
    releaseLock(key);
  }
}

向缓存中添加缓存项,并且唤醒其他读取该缓存项的线程

private void releaseLock(Object key) {
  // 刚开始看到这里的时候会有疑问,如果多个访问相同key的等待线程都被唤醒,然后并发执行,那么可能会并发地执行这个方法,从而可能会发生从locks中取出来的latch为Null,然后报错
  // 后面重新看了下acquireLock那里的代码,当线程被唤醒之后,并不会立即跳出循环,执行下面的流程,而是会在多个线程中选出一个线程来跳出循环,并且让其他等待线程继续等待这个线程创建的CountDownLatch
  CountDownLatch latch = locks.remove(key);
  if (latch == null) {
    throw new IllegalStateException("Detected an attempt at releasing unacquired lock. This should never happen.");
  }
  latch.countDown();
}

LRUCache

设定缓存项的最大个数,当缓存项的个数超过最大个数时,会将最近最少使用的缓存清退
这里主要利用的是java中的LinkedHashMap来实现的LRU

重要属性
private final Cache delegate;
// 下面的两个属性是为了实现LRU
private Map<Object, Object> keyMap;
private Object eldestKey;
重要方法
构造函数
public LruCache(Cache delegate) {
  this.delegate = delegate;
  setSize(1024);
}
public void setSize(final int size) {
  // 创建一个LinkedHashMap来实现LRU,通过重写removeEldestEntry,判断当前容量是否超过设定的阈值来判断是否移除最近最少使用的key
  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;
    }
  };
}
putObject
public void putObject(Object key, Object value) {
  // 添加缓存
  delegate.putObject(key, value);
  // 判断是否需要移除缓存项
  cycleKeyList(key);
}
private void cycleKeyList(Object key) {
  keyMap.put(key, key);
  if (eldestKey != null) {
    delegate.removeObject(eldestKey);
    eldestKey = null;
  }
}
getObject
public Object getObject(Object key) {
  keyMap.get(key); // touch
  return delegate.getObject(key);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值