Mybatis3.4源码-BlockingCache类

买了一本Mybatis技术内幕,学一学源码,记录一下缓存模块中BlockingCache这个类
Mybatis缓存模块运用了装饰器模式
缓存模块
其中decorators包下类均为装饰器,impl包下唯一的PrepetualCache类为Cache的基本实现。源码比较简单,在实现Cache接口的同时维护了一个HashMap和id。
Cache源码:

public interface Cache {

  String getId();

  void putObject(Object key, Object value);

  Object getObject(Object key);

  Object removeObject(Object key);

  void clear();

  int getSize();//myabtis内部未使用此方法

  ReadWriteLock getReadWriteLock();//从3.2.6版本开始myabtis内部不再使用该方法,缓存所需的任何锁定都必须​​由缓存提供程序内部提供

}

PrepetualCache类源码:

public class PerpetualCache implements Cache {

  private String id;

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

  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 ReadWriteLock getReadWriteLock() {
    return null;
  }

  @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源码:

public class BlockingCache implements Cache {

  private long timeout;//tryLock超时时间
  private final Cache delegate;//被装饰的对象
  private final ConcurrentHashMap<Object, ReentrantLock> locks;//维护了key和lock的对应关系

  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);//调用被装饰对象putObject方法缓存
    } finally {
      releaseLock(key);//释放key对应锁,重要!
    }
  }

  @Override
  public Object getObject(Object key) {
    acquireLock(key);//尝试获取key对应的锁
    Object value = delegate.getObject(key);//调用被装饰对象getObject获取key对应的value值
    if (value != null) {//注意,若value值为null,不会释放锁!这表明我们必须通过putObject方法添加key,value,才可以间接释放锁。
      releaseLock(key);
    }        
    return value;
  }

  @Override
  public Object removeObject(Object key) {
    // despite of its name, this method is called only to release locks
    //仅仅是释放锁,不会移除key对应的value
    releaseLock(key);
    return null;
  }

  @Override
  public void clear() {
    delegate.clear();//调用被装饰对象清空缓存
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }
  
  private ReentrantLock getLockForKey(Object key) {//该方法在acquireLock()中被调用,我们必须通过getObject()才可以创建key对应锁,若直接调用putObject()且key对应的锁不存在时会抛出NPE
    ReentrantLock lock = new ReentrantLock();
    ReentrantLock previous = locks.putIfAbsent(key, lock);//不存在key时put
    return previous == null ? lock : previous;
  }
  
  private void acquireLock(Object key) {
    Lock lock = getLockForKey(key);
    if (timeout > 0) {//timeout值通过set方法设置
      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()) {//这里可能会抛NPE,若先调用putObject,且key对应锁不存在时
      lock.unlock();
    }
  }

  public long getTimeout() {
    return timeout;
  }

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

测试示例:

package com.mybatis.coding.main;

import org.apache.ibatis.cache.decorators.BlockingCache;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.junit.Test;

import java.util.Objects;

public class CacheTest {

    @Test
    public void blockingCacheTest() throws Exception {
        String key = "name";
        String value = "zhangsan";
        final PerpetualCache delegate = new PerpetualCache("shenma");
        final BlockingCache cache = new BlockingCache(delegate);

        final Thread get = new Thread(() -> {
            System.out.println("get begin");
            final Object object = cache.getObject(key);
            System.out.println("get end object:" + object);
            if (Objects.isNull(object)) {
            	//若get出的值为null,则一定要put对应key和value,否则将导致锁一直被占用无法解锁,阻塞其他线程访问缓存
                cache.putObject(key, value);
                System.out.println("putObject   key:" + key + " value:" + value);
            }
        });
        get.start();
        final Thread get2 = new Thread(() -> {
            sleep(500);
            System.out.println("get2 begin");
            final Object object = cache.getObject(key);
            System.out.println("get2 end object:" + object);
        });
        get2.start();

        get.join();
        get2.join();
    }

    private void sleep(long sleepTime) {
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值