十、Mybatis 缓存系统解析

本文从以下几个方面介绍:

  • 1、如何开启 Mybatis 的缓存
  • 2、缓存的核心接口以及底层实现
  • 3、一级缓存的实现过程
  • 4、二级缓存的实现过程
  • 5、缓存的装饰器

前言

为了提高查询速度,减少数据库压力;Mybatis 提供了缓存功能,它分为一级缓存和二级缓存。

Mybatis 缓存系统的实现使用了 模板方法模式装饰器模式

1、如何开启 Mybatis 的缓存

1.1、一级缓存的作用

Mybatis 的一级缓存是会话级别的缓存,Mybatis 每创建一个 SqlSession 对象,就表示打开一次数据库会话,在一次会话中,应用程序很可能在短时间内反复执行相同的查询语句,如果不对数据进行缓存,则每查询一次就要执行一次数据库查询,这就造成数据库资源的浪费。又因为通过 SqlSession 执行的操作,实际上由 Executor 来完成数据库操作的,所以在 Executor 中会建立一个简单的缓存,即一级缓存;将每次的查询结果缓存起来,再次执行查询的时候,会先查询一级缓存,如果命中,则直接返回,否则再去查询数据库并放入缓存中。

1.2、一级缓存的开启以及生命周期

一级缓存的生命周期与 SqlSession 的生命周期相同,当调用 Executor.close 方法的时候,缓存变得不可用。一级缓存是默认开启的,一般情况下不需要特殊的配置,如果需要特殊配置,则可以通过插件的形式来实现

1.3、二级缓存的开启以及生命周期

Mybatis 提供的二级缓存是应用级别的缓存,它的生命周期和应用程序的生命周期相同,且与二级缓存相关的配置有以下 3 个

  • 1)、 mybatis-config.xml 配置文件中的 cacheEnabled 配置,它是二级缓存的总开关,只有该配置为 true ,后面的缓存配置才会生效。默认为 true,即二级缓存默认是开启的。
  <settings>
    <setting name="cacheEnabled" value="true"/>
  </settings>
  • 2)、Mapper.xml 配置文件中配置的 和 标签,如果 Mapper.xml 配置文件中配置了这两个标签中的任何一个,则表示开启了二级缓存的功能,如果配置了 标签,则在解析配置文件的时候,会为该配置文件指定的 namespace 创建相应的 Cache 对象作为其二级缓存(默认为 PerpetualCache 对象),如果配置了 节点,则通过 ref 属性的namespace值引用别的Cache对象作为其二级缓存。通过 和 标签来管理其在namespace中二级缓存功能的开启和关闭
  • 3)、 节点中的 useCache 属性也可以开启二级缓存,该属性表示查询的结果是否要存入到二级缓存中,该属性默认为 true,也就是说 标签默认会把查询结果放入到二级缓存中

img

2、缓存的核心接口

preview

2.1、Cache

Mybatis 使用 Cache 来表示缓存,它是一个接口,定义了缓存需要的一些方法

public interface Cache {
 //获取缓存的id,即 namespace
 String getId();
 // 添加缓存
 void putObject(Object key, Object value);
 //根据key来获取缓存对应的值
 Object getObject(Object key);
 // 删除key对应的缓存
 Object removeObject(Object key);
 // 清空缓存  
 void clear();
 // 获取缓存中数据的大小
 int getSize();
 //取得读写锁, 从3.2.6开始没用了
 ReadWriteLock getReadWriteLock();
}

2.2、PerpetualCache

Mybatis 为 Cache 接口提供的真正的实现类是 PerpetualCache,其他的只是应用装饰器模式,提供能额外功能,

如线程安全缓存 SynchronizedCache,它的底层存储还是 PerpetualCache。

PerpetualCache 的实现就是一个简单的 HashMap

public class PerpetualCache implements Cache {
 // id,一般对应mapper.xml 的namespace 的值
 private String id;
 
 // 用来存放数据,即缓存底层就是使用 map 来实现的
 private Map<Object, Object> cache = new HashMap<Object, Object>();
 
 public PerpetualCache(String id) {
 	this.id = id;
  }
 //......其他的getter方法.....
 // 添加缓存
 @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();
  }
}

2.3、CacheKey

Mybatis 的缓存使用了 key-value 的形式存入到 HashMap 中,而 key 的话,Mybatis 使用了 CacheKey 来表示 key,

它的生成规则为:mappedStementId + offset + limit + SQL + queryParams + environment生成一个哈希码.

public class CacheKey implements Cloneable, Serializable {

    private static final int DEFAULT_MULTIPLYER = 37;
    private static final int DEFAULT_HASHCODE = 17;

    // 参与计算hashcode,默认值为37
    private int multiplier;
    // CacheKey 对象的 hashcode ,默认值 17
    private int hashcode;
    // 检验和 
    private long checksum;
    // updateList 集合的个数
    private int count;
    // 由该集合中的所有对象来共同决定两个 CacheKey 是否相等
    private List<Object> updateList;

    public int getUpdateCount() {
        return updateList.size();
    }

    // 调用该方法,向 updateList 集合添加对应的对象
    public void update(Object object) {
        if (object != null && object.getClass().isArray()) {
            // 如果是数组,则循环处理每一项
            int length = Array.getLength(object);
            for (int i = 0; i < length; i++) {
                Object element = Array.get(object, i);
                doUpdate(element);
            }
        } else {
            doUpdate(object);
        }
    }

    // 计算 count checksum hashcode 和把对象添加到 updateList 集合中
    private void doUpdate(Object object) {
        int baseHashCode = object == null ? 1 : object.hashCode();
        count++;
        checksum += baseHashCode;
        baseHashCode *= count;
        hashcode = multiplier * hashcode + baseHashCode;

        updateList.add(object);
    }

    // 判断两个 CacheKey 是否相等
    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (!(object instanceof CacheKey)) {
            return false;
        }

        final CacheKey cacheKey = (CacheKey) object;

        if (hashcode != cacheKey.hashcode) {
            return false;
        }
        if (checksum != cacheKey.checksum) {
            return false;
        }
        if (count != cacheKey.count) {
            return false;
        }
        // 如果前几项都不满足,则循环遍历 updateList 集合,判断每一项是否相等,如果有一项不相等则这两个CacheKey不相等
        for (int i = 0; i < updateList.size(); i++) {
            Object thisObject = updateList.get(i);
            Object thatObject = cacheKey.updateList.get(i);
            if (thisObject == null) {
                if (thatObject != null) {
                    return false;
                }
            } else {
                if (!thisObject.equals(thatObject)) {
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public int hashCode() {
        return hashcode;
    }
}

CacheKey 的创建

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        //cacheKey 对象 
        CacheKey cacheKey = new CacheKey();
        // 向 updateList 存入id
        cacheKey.update(ms.getId());
        // 存入offset
        cacheKey.update(rowBounds.getOffset());
        // 存入limit
        cacheKey.update(rowBounds.getLimit());
        // 存入sql
        cacheKey.update(boundSql.getSql());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        for (ParameterMapping parameterMapping : parameterMappings) {
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                String propertyName = parameterMapping.getProperty();
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                Object value = metaObject.getValue(propertyName);
                // 存入每一个参数
                cacheKey.update(value);
            }
        }
        if (configuration.getEnvironment() != null) {
            // 存入 environmentId
            cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
    }

3、一级缓存的实现

Mybatis 的一级缓存是在 BaseExecutor 中实现的

3.1、Executor

Executor 接口定义了操作数据库的基本方法,我们用 SqlSession 来执行 sql时,其实是操作了 Executor 的相关方法

public interface Executor {

    ResultHandler NO_RESULT_HANDLER = null;

    // insert | update | delete 的操作方法
    int update(MappedStatement ms, Object parameter) throws SQLException;

    // 查询,带分页,带缓存  
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

    // 查询,带分页 
    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

    // 查询存储过程
    <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

    //刷新批处理语句
    List<BatchResult> flushStatements() throws SQLException;

    // 事务提交
    void commit(boolean required) throws SQLException;

    // 事务回滚
    void rollback(boolean required) throws SQLException;

    // 创建缓存的key
    CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

    // 是否缓存
    boolean isCached(MappedStatement ms, CacheKey key);

    // 清空缓存
    void clearLocalCache();

    // 延迟加载
    void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

    // 获取事务
    Transaction getTransaction();
}

3.2、BaseExecutor

BaseExecutor 是一个抽象类,实现了 Executor 接口的所有方法,使用了模板模式策略模式,抽象了 (doUpdate, doQuery, doQueryCursor, doFlushStatement)4个核心方法用来执行sql,它们由不同的子类实现

Mybatis 的一级缓存就是在该类中实现的。

具体代码实现如下

	@Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            queryStack++;
            // 从缓存中获取结果
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // 缓存中获取不到,则调用queryFromDatabase()方法从数据库中查询
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            queryStack--;
        }
        if (queryStack == 0) {
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            deferredLoads.clear();
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482
                clearLocalCache();
            }
        }
        return list;
    }

 	private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // 调用doQuery()方法查询
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            localCache.removeObject(key);
        }
        // 缓存查询结果
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }

4、二级缓存的实现

Mybatis 的二级缓存是用 CachingExecutor 来实现的,它是 Executor 的一个装饰器类。为 Executor 对象添加了缓存的功能。

在介绍 CachingExecutor 之前,先来看看 CachingExecutor 依赖的两个类,TransactionalCacheManager 和 TransactionalCache。

4.1、TransactionalCache

TransactionalCache 实现了 Cache 接口,主要用于保存在某个 SqlSession 的某个事务中需要向某个二级缓存中添加的数据,代码如下:

public class TransactionalCache implements Cache {
    // 底层封装的二级缓存对应的Cache对象
    private Cache delegate;
    // 为true时,表示当前的 TransactionalCache 不可查询,且提交事务时会清空缓存
    private boolean clearOnCommit;
    // 存放需要添加到二级缓存中的数据
    private Map<Object, Object> entriesToAddOnCommit;
    // 存放未命中缓存的 CacheKey 对象
    private Set<Object> entriesMissedInCache;

    public TransactionalCache(Cache delegate) {
        this.delegate = delegate;
        this.clearOnCommit = false;
        this.entriesToAddOnCommit = new HashMap<Object, Object>();
        this.entriesMissedInCache = new HashSet<Object>();
    }

    // 添加缓存数据的时候,先暂时放到 entriesToAddOnCommit 集合中,在事务提交的时候,再把数据放入到二级缓存中,避免脏数据
    @Override
    public void putObject(Object key, Object object) {
        entriesToAddOnCommit.put(key, object);
    }
    // 提交事务,
    public void commit() {
        if (clearOnCommit) {
            delegate.clear();
        }
        // 把 entriesToAddOnCommit  集合中的数据放入到二级缓存中
        flushPendingEntries();
        reset();
    }
    // 把 entriesToAddOnCommit  集合中的数据放入到二级缓存中
    private void flushPendingEntries() {
        for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
            // 放入到二级缓存中
            delegate.putObject(entry.getKey(), entry.getValue());
        }
        for (Object entry : entriesMissedInCache) {
            if (!entriesToAddOnCommit.containsKey(entry)) {
                delegate.putObject(entry, null);
            }
        }
    }
    // 事务回滚
    public void rollback() {
        // 把未命中缓存的数据清除掉
        unlockMissedEntries();
        reset();
    }
    private void unlockMissedEntries() {
        for (Object entry : entriesMissedInCache) {
            delegate.removeObject(entry);
        }
    }

4.2、TransactionalCacheManager

TransactionalCacheManager 用于管理 CachingExecutor 使用的二级缓存:

public class TransactionalCacheManager {
 
 //用来管理 CachingExecutor 使用的二级缓存
 // key 为对应的CachingExecutor 使用的二级缓存
 // value 为对应的 TransactionalCache 对象
 private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
 
 public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }
 public Object getObject(Cache cache, CacheKey key) {
 return getTransactionalCache(cache).getObject(key);
  }  
 public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }
 public void commit() {
 for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }
 public void rollback() {
 for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }
 // 所有的调用都会调用 TransactionalCache 的方法来实现
 private TransactionalCache getTransactionalCache(Cache cache) {
    TransactionalCache txCache = transactionalCaches.get(cache);
 if (txCache == null) {
      txCache = new TransactionalCache(cache);
      transactionalCaches.put(cache, txCache);
    }
 return txCache;
  }
 
}

4.3、CachingExecutor

接下来看下 二级缓存的实现 CachingExecutor :主要是通过 TransactionalCache 来操作二级缓存的。

public class CachingExecutor implements Executor {
    // 底层的 Executor
    private Executor delegate;
    private TransactionalCacheManager tcm = new TransactionalCacheManager();

    // 查询方法
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取 SQL
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 创建缓存key,在CacheKey中已经分析过创建过程
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

    // 查询
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
            throws SQLException {
        // 获取查询语句所在namespace对应的二级缓存
        Cache cache = ms.getCache();
        // 是否开启了二级缓存
        if (cache != null) {
            // 根据 <select> 的属性 useCache 的配置,决定是否需要清空二级缓存
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                // 二级缓存不能保存输出参数,否则抛异常
                ensureNoOutParams(ms, parameterObject, boundSql);
                // 从二级缓存中查询对应的值
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    // 如果二级缓存没有命中,则调用底层的 Executor 查询,其中会先查询一级缓存,一级缓存也未命中,才会去查询数据库
                    list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    // 查询到的数据放入到二级缓存中去
                    tcm.putObject(cache, key, list); // issue #578 and #116
                }
                return list;
            }
        }
        // 如果没有开启二级缓存,则直接调用底层的 Executor 查询,还是会先查一级缓存
        return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

img

5、缓存的装饰器

Cache 接口由很多的装饰器类,共 10 个,添加了不同的功能,如下所示:后面列了几个,具体详情请参考源码

img

5.1、SynchronizedCache

  • 线程安全的缓存,使用关键字 synchronized 来保证线程安全
public class SynchronizedCache implements Cache {
 
 private Cache delegate;
 
 public SynchronizedCache(Cache delegate) {
 this.delegate = delegate;
  }
 @Override
 public synchronized int getSize() {
 return delegate.getSize();
  }
 @Override
 public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }
 @Override
 public synchronized Object getObject(Object key) {
 return delegate.getObject(key);
  }
 
 @Override
 public synchronized Object removeObject(Object key) {
 return delegate.removeObject(key);
  }
 // ............
}

5.2、LruCache

  • 最近最少使用缓存:最近使用最少的缓存优先删除
package com.luo.ibatis.cache.decorators;

import com.luo.ibatis.cache.Cache;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;

/**
 * @author :archer
 * @date :Created in 2021/6/30 21:01
 * @description:
 * Lru (least recently used) cache decorator
 * 最近最少使用缓存
 * 最近没怎么使用的的缓存优先删除
 */
public class LruCache implements Cache {

    private final Cache delegate;
    private Map<Object, Object> keyMap;
    private Object eldestKey;

    public LruCache(Cache delegate) {
        this.delegate = delegate;
        setSize(1024);
    }

    @Override
    public String getId() {
        return delegate.getId();
    }

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

    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;
            }
        };
    }

    @Override
    public void putObject(Object key, Object value) {
        delegate.putObject(key, value);
        cycleKeyList(key);
    }

    @Override
    public Object getObject(Object key) {
        keyMap.get(key); //touch
        return delegate.getObject(key);
    }

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

    @Override
    public void clear() {
        delegate.clear();
        keyMap.clear();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    private void cycleKeyList(Object key) {
        keyMap.put(key, key);
        if (eldestKey != null) {
            delegate.removeObject(eldestKey);
            eldestKey = null;
        }
    }
}

5.3、FifoCache

  • 先进先出 先添加的缓存优先删除
package com.luo.ibatis.cache.decorators;

import com.luo.ibatis.cache.Cache;

import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.locks.ReadWriteLock;

/**
 * @author :archer
 * @date :Created in 2021/6/30 21:23
 * @description: 先进先出 先添加的缓存优先删除
 * FIFO (first in, first out) cache decorator
 */
public class FifoCache implements Cache {

    private final Cache delegate;
    private final Deque<Object> keyList;
    private int size;

    public FifoCache(Cache delegate) {
        this.delegate = delegate;
        this.keyList = new LinkedList<Object>();
        this.size = 1024;
    }

    @Override
    public String getId() {
        return delegate.getId();
    }

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

    public void setSize(int size) {
        this.size = size;
    }

    @Override
    public void putObject(Object key, Object value) {
        cycleKeyList(key);
        delegate.putObject(key, value);
    }

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

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

    @Override
    public void clear() {
        delegate.clear();
        keyList.clear();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    private void cycleKeyList(Object key) {
        keyList.addLast(key);
        if (keyList.size() > size) {
            Object oldestKey = keyList.removeFirst();
            delegate.removeObject(oldestKey);
        }
    }
}

5.4、SoftCache

  • 软引用缓存:jvm 堆内存不够时,自动删除
  • 软引用:用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
package com.luo.ibatis.cache.decorators;

import com.luo.ibatis.cache.Cache;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.locks.ReadWriteLock;

/**
 * @author :archer
 * @date :Created in 2021/6/30 21:27
 * @description:软引用缓存装饰器
 * Soft Reference cache decorator
 * Thanks to Dr. Heinz Kabutz for his guidance(指导) here.
 */
public class SoftCache implements Cache {
    private final Deque<Object> hardLinksToAvoidGarbageCollection;
    private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
    private final Cache delegate;
    private int numberOfHardLinks;

    public SoftCache(Cache delegate) {
        this.delegate = delegate;
        this.numberOfHardLinks = 256;
        this.hardLinksToAvoidGarbageCollection = new LinkedList<Object>();
        this.queueOfGarbageCollectedEntries = new ReferenceQueue<Object>();
    }

    @Override
    public String getId() {
        return delegate.getId();
    }

    @Override
    public int getSize() {
        removeGarbageCollectedItems();
        return delegate.getSize();
    }


    public void setSize(int size) {
        this.numberOfHardLinks = size;
    }

    @Override
    public void putObject(Object key, Object value) {
        removeGarbageCollectedItems();
        delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
    }

    @Override
    public Object getObject(Object key) {
        Object result = null;
        @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
        SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
        if (softReference != null) {
            result = softReference.get();
            if (result == null) {
                delegate.removeObject(key);
            } else {
                // See #586 (and #335) modifications need more than a read lock
                synchronized (hardLinksToAvoidGarbageCollection) {
                    hardLinksToAvoidGarbageCollection.addFirst(result);
                    if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
                        hardLinksToAvoidGarbageCollection.removeLast();
                    }
                }
            }
        }
        return result;
    }

    @Override
    public Object removeObject(Object key) {
        removeGarbageCollectedItems();
        return delegate.removeObject(key);
    }

    @Override
    public void clear() {
        synchronized (hardLinksToAvoidGarbageCollection) {
            hardLinksToAvoidGarbageCollection.clear();
        }
        removeGarbageCollectedItems();
        delegate.clear();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    private void removeGarbageCollectedItems() {
        SoftEntry sv;
        while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
            delegate.removeObject(sv.key);
        }
    }

    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;
        }
    }

}

5.5、WeakCache

  • 弱引用缓存
  • 弱引用:用来描述非必须对象的,他的强度比软引用更弱一些,被弱引用关联的对象,在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收。
package com.luo.ibatis.cache.decorators;

import com.luo.ibatis.cache.Cache;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.locks.ReadWriteLock;

/**
 * @author :archer
 * @date :Created in 2021/6/30 22:02
 * @description: 软引用缓存装饰器
 * Weak Reference cache decorator.
 * Thanks to Dr. Heinz Kabutz for his guidance here.
 */
public class WeakCache implements Cache {
    private final Deque<Object> hardLinksToAvoidGarbageCollection;
    private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
    private final Cache delegate;
    private int numberOfHardLinks;

    public WeakCache(Cache delegate) {
        this.delegate = delegate;
        this.numberOfHardLinks = 256;
        this.hardLinksToAvoidGarbageCollection = new LinkedList<Object>();
        this.queueOfGarbageCollectedEntries = new ReferenceQueue<Object>();
    }

    @Override
    public String getId() {
        return delegate.getId();
    }

    @Override
    public int getSize() {
        removeGarbageCollectedItems();
        return delegate.getSize();
    }

    public void setSize(int size) {
        this.numberOfHardLinks = size;
    }

    @Override
    public void putObject(Object key, Object value) {
        removeGarbageCollectedItems();
        delegate.putObject(key, new WeakEntry(key, value, queueOfGarbageCollectedEntries));
    }

    @Override
    public Object getObject(Object key) {
        Object result = null;
        @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
        WeakReference<Object> weakReference = (WeakReference<Object>) delegate.getObject(key);
        if (weakReference != null) {
            result = weakReference.get();
            if (result == null) {
                delegate.removeObject(key);
            } else {
                hardLinksToAvoidGarbageCollection.addFirst(result);
                if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
                    hardLinksToAvoidGarbageCollection.removeLast();
                }
            }
        }
        return result;
    }

    @Override
    public Object removeObject(Object key) {
        removeGarbageCollectedItems();
        return delegate.removeObject(key);
    }

    @Override
    public void clear() {
        hardLinksToAvoidGarbageCollection.clear();
        removeGarbageCollectedItems();
        delegate.clear();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    private void removeGarbageCollectedItems() {
        WeakEntry sv;
        while ((sv = (WeakEntry) queueOfGarbageCollectedEntries.poll()) != null) {
            delegate.removeObject(sv.key);
        }
    }

    private static class WeakEntry extends WeakReference<Object> {
        private final Object key;

        private WeakEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
            super(value, garbageCollectionQueue);
            this.key = key;
        }
    }

}

5.6、ScheduledCache

  • 超时自动失效的缓存:自动失效的时间点不是时间到了,而是下次查询缓存时,如果判断时间过期才会失效,所以并不是真正的超时自动失效的缓存
package com.luo.ibatis.cache.decorators;

import com.luo.ibatis.cache.Cache;

import java.util.concurrent.locks.ReadWriteLock;

/**
 * @author :archer
 * @date :Created in 2021/6/30 21:26
 * @description:
 */
public class ScheduledCache implements Cache {

    private final Cache delegate;
    protected long clearInterval;
    protected long lastClear;

    public ScheduledCache(Cache delegate) {
        this.delegate = delegate;
        this.clearInterval = 60 * 60 * 1000; // 1 hour
        this.lastClear = System.currentTimeMillis();
    }

    public void setClearInterval(long clearInterval) {
        this.clearInterval = clearInterval;
    }

    @Override
    public String getId() {
        return delegate.getId();
    }

    @Override
    public int getSize() {
        clearWhenStale();
        return delegate.getSize();
    }

    @Override
    public void putObject(Object key, Object object) {
        clearWhenStale();
        delegate.putObject(key, object);
    }

    @Override
    public Object getObject(Object key) {
        return clearWhenStale() ? null : delegate.getObject(key);
    }

    @Override
    public Object removeObject(Object key) {
        clearWhenStale();
        return delegate.removeObject(key);
    }

    @Override
    public void clear() {
        lastClear = System.currentTimeMillis();
        delegate.clear();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    @Override
    public int hashCode() {
        return delegate.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        return delegate.equals(obj);
    }

    private boolean clearWhenStale() {
        if (System.currentTimeMillis() - lastClear > clearInterval) {
            clear();
            return true;
        }
        return false;
    }

}

5.7、BlockingCache

  • 阻塞缓存:当缓存不存在阻塞,直到有缓存才返回
package com.luo.ibatis.cache.decorators;

import com.luo.ibatis.cache.Cache;
import com.luo.ibatis.cache.CacheException;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author :archer
 * @date :Created in 2021/6/30 21:22
 * @description: 简单阻塞,缓存中找不到会阻塞其他线程(效率低下)
 * Simple blocking decorator
 *
 * Simple and inefficient version of EhCache's BlockingCache decorator.
 * It sets a lock over a cache key when the element is not found in cache.
 * This way, other threads will wait until this element is filled instead of hitting the database.
 */
public class BlockingCache implements Cache {

    private long timeout;
    private final Cache delegate;
    private final ConcurrentHashMap<Object, ReentrantLock> locks;

    public BlockingCache(Cache delegate) {
        this.delegate = delegate;
        this.locks = new ConcurrentHashMap<Object, ReentrantLock>();
    }

    @Override
    public String getId() {
        return delegate.getId();
    }

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

    @Override
    public void putObject(Object key, Object value) {
        try {
            delegate.putObject(key, value);
        } finally {
            releaseLock(key);
        }
    }

    @Override
    public Object getObject(Object key) {
        acquireLock(key);
        Object value = delegate.getObject(key);
        if (value != null) {
            releaseLock(key);
        }
        return value;
    }

    @Override
    public Object removeObject(Object key) {
        // despite of its name, this method is called only to release locks
        releaseLock(key);
        return null;
    }

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

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    private ReentrantLock getLockForKey(Object key) {
        ReentrantLock lock = new ReentrantLock();
        ReentrantLock previous = locks.putIfAbsent(key, lock);
        return previous == null ? lock : previous;
    }

    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 void releaseLock(Object key) {
        ReentrantLock lock = locks.get(key);
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }

    public long getTimeout() {
        return timeout;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值