使用虚引用的一种应用——解决队列数量的限制

使用虚引用的一种应用——解决队列数量的限制

没事就喜欢研究这些东西,于是对之前做的使用队列解决缓存一致性有了新的思考。下面只是记录我的思考思路,欢迎大家一起来讨论。

思考

在上篇博客中,有具体的给出了使用ReentrantLock来解决缓存一致性的方案:

在上个方案中,已经可以利用ReentrantLock来代替线程+阻塞队列实现的消费模式。这样做的有点就是不需要开启多余的线程进行消费,可以节省线程资源,并且可以放心大胆的设置更多的锁,让锁的粒度更小一点。那么有没有一种方式可以让锁的粒度更小呢?或者说不受制于数量限制呢?

最开始想的是每一个请求过来时,通过hash去路由获取到指定的锁,每一个请求,只要key不同都能获取到不同的锁,那么在进行同步操作的时候锁的粒度是最小的。但是为了让对于相同key的请求能够获取到同一个锁,我们需要用一个HashMap来存储hash到锁的映射关系,每次请求都会过来先判断系统里有没有这个key的锁,有的话则取这个锁,没有的话则新建一个锁并存入到这个HashMap中去。

那么现在就会有一个问题,随着访问量不断增多,存储在HashMap中的数据也会越来越大,我们必须想办法让没有在使用或者不需要使用的锁被GC回收掉,但是大家都知道,在HashMap中存入的锁是不会被GC回收的。

因此,接下来的一些想法就是着重于解决怎么去让JVM正确回收掉这些数据的问题。

一些想法

既然是由于HashMap是强引用,不能解决问题,那么第一时间想到的便是WeakHashMap,这个使用虚引用的WeakHashMap有一个特点,就是里面的Key都是虚引用,当这个Map中的Key不再使用时,即这个Key没有其他地方再对它有强引用时,发生GC时便会将他回收掉。

很显然,我们要的就是这种效果,然而在这个例子中,我们需要的并不是Key不再使用时将其回收掉,而是WeakHashMap中的值,因为Key基本是一直没有强引用的,只要一发生GC就会将这个键值对从Map中移除,而这个时候Key没有用了,值可能还是在使用的,这与我们的预期不太一样。

我们的预期是,Map中的值ReentrantLock不再使用时(即不再有强引用时),将其回收掉,所以目前的问题就在于如何解决WeakHashMap中的值没有引用时就将他回收的问题。

方案

(1)、JVM的根可达性算法,将Value挂上对Key的强引用。
当Value存在强引用时,会使得Key也不会被当做垃圾。
因为 KEY——>Value——>某个强引用——>root。
而当Value的某个强引用消失时,即Value不再被使用时,在发生垃圾回收时,Key在这一条链路到达不了root,所以也就变成垃圾可以回收了

(2)、取消WeakHashMap对Value的强引用。
因为WeakHashMap是对Value有强引用的,如果不取消的话就会一直有强引用存在,那么这个键值对是不能被回收的。因此这个Value还要包装一层WeakRenference。

更改后相关的代码如下:

  • 查询请求类:
public class QueryRequest<T> extends Request<T> {

    private QueryFunction<T> queryFunction;

    public QueryRequest(String key, QueryFunction<T> queryFunction) {
        this.key = key;
        this.queryFunction = queryFunction;
    }

    @Override
    public void execute() {
        if (queryFunction != null) {
            result = queryFunction.queryExecution(key);
        }
    }
}
  • 更新请求类
public class UpdateRequest<T> extends Request<T> {
    private T value;
    private UpdateFunction<T> updateFunction;

    public UpdateRequest(String key, T value, UpdateFunction<T> updateFunction) {
        this.key = key;
        this.value = value;
        this.updateFunction = updateFunction;
    }

    @Override
    public void execute() {
        if (updateFunction != null) {
            updateFunction.updateExecution(key, value);
        }
    }
}
  • 查找请求方法的类:
public interface QueryFunction<T> {
    /**
     * 需要执行查询的动作,一般是查数据库和写入redis的操作
     *
     * @param key 要查询的key
     * @return 查询得到的值
     */
    public T queryExecution(String key);
}
  • 更新请求方法的类
public interface UpdateFunction<T> {
    /**
     * 需要执行查询的动作,一般是查数据库和写入redis的操作
     *
     * @param key   要更新的key
     * @param value 要更新的value
     */
    public void updateExecution(String key, T value);
}
  • 锁管理类
public class LockerManager {
    public final Map<Integer, WeakReference<WeakLocker>> lockMap = new WeakHashMap<>();
    private static final LockerManager manager = new LockerManager();

    public static LockerManager getInstance() {
        return manager;
    }


    /**
     * 获取该请求对应的锁
     *
     * @param request 请求
     * @return
     */
    public WeakLocker getLocker(Request request) {
        int h = request.getKey().hashCode();
        int index = request.getKey() == null ? 0 : h ^ (h >>> 16);
        WeakReference<WeakLocker> reference;
        synchronized (this) {
            reference = lockMap.get(index);
            if (Objects.isNull(reference)) {
                ReentrantLock lock = new ReentrantLock();
                Integer key = new Integer(index);
                WeakLocker locker = new WeakLocker(key, lock);
                reference = new WeakReference<>(locker);
                lockMap.put(key, reference);
            }
        }
        return reference.get();
    }
}
  • 虚引用锁
public class WeakLocker {
    /** 仅仅只是为了给这个key添加一个强引用 */
    private final Integer key;
    private final ReentrantLock lock;
    public WeakLocker(Integer key,ReentrantLock lock){
        this.key=key;
        this.lock=lock;
    }
    public ReentrantLock getLock(){
        return lock;
    }
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException{
        return lock.tryLock(timeout,unit);
    }
    public void unlock(){
        lock.unlock();
    }
}
@Service("redisQueueService2")
public class RedisQueueService {

    /**
     * 先从redis缓存获取,获取不到,再执行该方法
     *
     * @param key           要查询的key
     * @param timeout       超时时间,以毫秒为单位
     * @param queryFunction 定义查询操作
     * @return
     */
    public <T> T query(String key, long timeout, QueryFunction<T> queryFunction) throws Throwable {
        Request<T> request = new QueryRequest<>(key, queryFunction);
        WeakLocker lock = LockerManager.getInstance().getLocker(request);
        try {
            if (lock.tryLock(timeout, TimeUnit.MILLISECONDS)) {
                request.execute();
            } else {
                System.out.println("超时");
                throw new Throwable("查询超时");
            }
        } finally {
            lock.unlock();
        }
        return request.getResult();
    }

    /**
     * 执行更新操作
     *
     * @param key            要更新的key
     * @param value          要更新的值
     * @param timeout        超时时间,以毫秒为单位
     * @param updateFunction 定义更新操作
     */
    public <T> void update(String key, T value, long timeout, UpdateFunction<T> updateFunction) throws Throwable {
        Request<T> request = new UpdateRequest<>(key, value, updateFunction);
        WeakLocker lock = LockerManager.getInstance().getLocker(request);
        try {
            if (lock.tryLock(timeout, TimeUnit.MILLISECONDS)) {
                request.execute();
            } else {
                System.out.println("超时");
                throw new Throwable("更新超时");
            }
        } finally {
            lock.unlock();
        }
    }
}

结束

以上,就能够实现每一个相同的key一定会获取到同一个锁,不同的key除非hash相同,不然都获取到的是不同的锁,并且有JVM帮助自动回收多余的、不需要的锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值