Mybatis源码-BlockingCache(阻塞式缓存装饰类)

本文深入探讨了Mybatis中BlockingCache缓存实现机制,详细解释了如何使用ReentrantLock进行加锁,确保线程安全地获取和更新缓存数据。每个缓存key都拥有独立的锁,防止并发冲突。
摘要由CSDN通过智能技术生成

今天学习一下Mybatis的cache包的源码,理解BlockingCache缓存实现

1、贴源码+注释

/**
 *    Copyright 2009-2018 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.decorators;

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;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
import org.apache.ibatis.cache.impl.PerpetualCache;

/**
 * 简单阻塞缓存装饰类
 * 这是一个简单低效EhCache阻塞缓存版本,如果某个缓存key的值不存在就会锁定该缓存,这些线程
 * 将会等到这个key对应值被设置进来,而不会去查询数据库
 * 1、他没有实现getReadWriteLock方法
 * 2、没有值,它会一直用ReentrantLock 锁定,直到该线程主动释放这个锁,否则一直占用,其他线程是不可以解锁,解铃还须系铃人
 * 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.
 *
 * @author Eduardo Macarron
 *
 */
public class BlockingCache implements Cache {

  /**
   * 超时时间
   */
  private long timeout;

  /**
   * 委托缓存
   */
  private final Cache delegate;

  /**
   * 通过并行hashMap存储ReentrantLock对象
   */
  private final ConcurrentHashMap<Object, ReentrantLock> locks;

  /**
   * 传入委托缓存
   * @param delegate
   */
  public BlockingCache(Cache delegate) {
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<>();
  }

  /**
   * 获取委托缓存Id
   * @return
   */
  @Override
  public String getId() {
    return delegate.getId();
  }

  /**
   * 委托缓存大小
   * @return
   */
  @Override
  public int getSize() {
    return delegate.getSize();
  }

  /**
   * 设置缓存
   * @param key Can be any object but usually it is a {@link CacheKey}
   * @param value The result of a select.
   */
  @Override
  public void putObject(Object key, Object value) {
    try {
      delegate.putObject(key, value);
    } finally {
      releaseLock(key);
    }
  }

  /**
   * 获取缓存对象
   * @param key The key
   * @return
   */
  @Override
  public Object getObject(Object key) {
    //获取锁
    //如果对象有值释放锁
    //不然一直占用锁
    acquireLock(key);
    Object value = delegate.getObject(key);
    if (value != null) {
      releaseLock(key);
    }
    return value;
  }

  /**
   * 手动释放锁,当前线程暂用的锁
   * @param key The key
   * @return
   */
  @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;
  }

  /**
   * 通过key 获取ReentrantLock,没有就新增
   * @param key
   * @return
   */
  private ReentrantLock getLockForKey(Object key) {
    return locks.computeIfAbsent(key, k -> new ReentrantLock());
  }

  private void acquireLock(Object key) {
    //获取lock,
    //是否设置超时时间,有的就尝试lock(毫秒),否则直接锁定
    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();
      System.out.println("加锁 getThread : " + Thread.currentThread().getName());
    }
  }

  /**
   * 释放锁, 锁定key
   * @param key
   */
  private void releaseLock(Object key) {
    // 获取当前缓存key,value 对应锁, ReentrantLock
    //判断该key对应锁是否为当前线程占用,有则释放
    ReentrantLock lock = locks.get(key);
    if (lock.isHeldByCurrentThread()) {
      lock.unlock();
      System.out.println("释放锁 getThread : " + Thread.currentThread().getName());
    }
  }

  public long getTimeout() {
    return timeout;
  }

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


  /**
   * 测试main方法
   * ReentrantLock 表示重入锁,同一个线程加锁几次,需要解锁几次,例如加锁10次,解锁也要10次,否则其他线程不能获取锁
   * @param args
   * @throws InterruptedException
   */
  public static void main(String[] args) throws InterruptedException {
    BlockingCache blockingCache = new BlockingCache(new PerpetualCache("111111"));
    String key = "2222";
    Thread putThread = new Thread(()->{
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
      }
      blockingCache.putObject(key, "111111");
    });
    putThread.start();
    // 一直到获取对象的值才结束,然后统一释放锁
    Thread getThread = new Thread(()->{
      int unlockCount = 0;
      long start = System.currentTimeMillis();
      System.out.println("开始时间:" + start + ", getThread : " + Thread.currentThread().getName());
      while (blockingCache.getObject(key) == null) {
        unlockCount++;
      }
      System.out.println("结束时间:" + (System.currentTimeMillis() - start) + ", getThread : " + Thread.currentThread().getName());
      //释放锁
      for (int i = 0; i< unlockCount; i++) {
        blockingCache.releaseLock(key);
      }
    });
    getThread.start();
    //尝试获取这个锁,发现被getThread占用了,只能排队了
    Thread tryLock = new Thread(()->{
      //让tryLock线程慢于getThread线程
      try {
        Thread.sleep(500);
      } catch (InterruptedException e) {
      }
      long start = System.currentTimeMillis();
      System.out.println("开始时间:" + start + ", tryLock : " + Thread.currentThread().getName());
      blockingCache.getObject(key);
      System.out.println("结束时间:" + (System.currentTimeMillis() - start) + ", tryLock : " + Thread.currentThread().getName());
    });
    tryLock.start();
    putThread.join();
    getThread.join();
    tryLock.join();
  }

}

2、总结

  • 这个类使用ReentrantLock进行加锁
  • 解铃还须系铃人,thread自己加锁,也要自己解锁,否则其他线程一直处于等待状态
  • 使用getObject(key)获取对应key的缓存对象,如果对象不存在就会直接返回null,但是该线程加了锁,必须要该线程主动释放锁,其他线程才可以获取getObject(key)的的缓存对象
  • 每一个缓存key都有一个锁(ReentrantLock),不会相互影响
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值