意义
基于2Q缓存算法,实现本地更细粒度的、更低消耗的本地锁复用,分布式环境下可用于分布式锁的前置锁,减少分布式锁的请求次数和资源消耗
工作原理
当数据第一次访问时,2Q算法将数据缓存在FIFO队列里面,当数据第二次被访问时,则将数据从FIFO队列移到LRU队列里面,两个队列各自按照自己的方法淘汰数据。详细实现如下:
(1). 新访问的数据(LRU队列中不存在)插入到FIFO队列;
(2). 如果数据在FIFO队列中一直没有被再次访问,则最终按照FIFO规则淘汰;
(3). 如果数据在FIFO队列中被再次访问,则将数据移到LRU队列头部;
(4). 如果数据在LRU队列再次被访问,则将数据移到LRU队列头部;
(5). LRU队列淘汰末尾的数据。
实现
package com.tsd.dlmPlatform_common.util;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author hejun
* @version v1.0
* @Description 用于更细颗粒度控制ReentrantLock,并支持锁池2Q策略的工具类
* @date 2023/3/2
*/
@Slf4j
public class LockUtil {
// 锁池LRU缓存最大容量
private static final int DEFAULT_LRU_MAX_SIZE = 64;
// 锁池FIFO缓存最大容量
private static final int DEFAULT_FIFO_MAX_SIZE = 64;
// 初始锁池名称
private static final String DEFAULT_POOL_NAME = "DEFAULT_LOCK_POOL";
// 支持分段上锁的2Q策略锁池
private static volatile Map<String, TwoQuesLockPool> POOL_MAP = new HashMap<>();
/**
* 获取指定锁
* @param poolName 锁池名称(控制缓存重排序颗粒度)
* @param key 锁名称(控制锁颗粒度)
* @param fifoMaxSize fifo锁池最大容量
* @param lruMaxSize lru锁池最大容量
* @param isFair 是否公平
* @return
*/
public static ReentrantLock getLock(String poolName, String key, int fifoMaxSize, int lruMaxSize, boolean isFair){
if(poolName == null || key == null || fifoMaxSize == 0 || lruMaxSize == 0){
throw new RuntimeException("LockUtil.getLock() 方法参数有误");
}
return getOrAddLockPool(poolName, fifoMaxSize, lruMaxSize).getLock(key, isFair);
}
/**
* 获取指定锁
* @param poolName 锁池名称(控制缓存重排序颗粒度)
* @param key 锁名称(控制锁颗粒度)
* @param fifoMaxSize fifo锁池最大容量
* @param lruMaxSize lru锁池最大容量
* @return
*/
public static ReentrantLock getLock(String poolName, String key, int fifoMaxSize, int lruMaxSize){
return getLock(DEFAULT_POOL_NAME, key, fifoMaxSize, lruMaxSize, false);
}
/**
* 获取指定锁
* @param key 锁名称(控制锁颗粒度)
* @param fifoMaxSize fifo锁池最大容量
* @param lruMaxSize lru锁池最大容量
* @param isFair 是否公平
* @return
*/
public static ReentrantLock getLock(String key, int fifoMaxSize, int lruMaxSize, boolean isFair){
return getLock(DEFAULT_POOL_NAME, key, fifoMaxSize, lruMaxSize, isFair);
}
/**
* 获取指定锁
* @param key 锁名称(控制锁颗粒度)
* @param fifoMaxSize fifo锁池最大容量
* @param lruMaxSize lru锁池最大容量
* @return
*/
public static ReentrantLock getLock(String key, int fifoMaxSize, int lruMaxSize){
return getLock(DEFAULT_POOL_NAME, key, fifoMaxSize, lruMaxSize, false);
}
/**
* 获取指定锁
* @param poolName 锁池名称(控制缓存重排序颗粒度)
* @param key 锁名称(控制锁颗粒度)
* @param isFair 是否公平
* @return
*/
public static ReentrantLock getLock(String poolName, String key, boolean isFair){
return getLock(poolName, key, DEFAULT_FIFO_MAX_SIZE, DEFAULT_LRU_MAX_SIZE, isFair);
}
/**
* 获取指定锁
* @param poolName 锁池名称(控制缓存重排序颗粒度)
* @param key 锁名称(控制颗粒度)
* @return
*/
public static ReentrantLock getLock(String poolName, String key){
return getLock(poolName, key, DEFAULT_FIFO_MAX_SIZE, DEFAULT_LRU_MAX_SIZE, false);
}
/**
* 获取指定锁
* @param key 锁名称(控制颗粒度)
* @return
*/
public static ReentrantLock getLock(String key){
return getLock(DEFAULT_POOL_NAME, key, DEFAULT_FIFO_MAX_SIZE, DEFAULT_LRU_MAX_SIZE, false);
}
/**
* 获得指定名称的锁池,如果没有就创建
* @param pooName
* @param fifoMaxSize
* @param lruMaxSize
* @return
*/
private static TwoQuesLockPool getOrAddLockPool(String pooName, int fifoMaxSize, int lruMaxSize){
TwoQuesLockPool lockPool = POOL_MAP.get(pooName);
// 双检锁的方式检查锁池是否存在
if(lockPool == null){
synchronized (pooName.intern()){
lockPool = POOL_MAP.get(pooName);
if(lockPool == null){
lockPool = new TwoQuesLockPool(fifoMaxSize, lruMaxSize);
POOL_MAP.put(pooName, lockPool);
}
}
}
return lockPool;
}
/**
* 锁缓存池类,accessOrder 为true则构造LRU锁池,为false则构造FIFO锁池
* @param <K>
* @param <V>
*/
@Getter
private static class LockCachePool<K,V> extends LinkedHashMap<K,V> {
// 触发回收的缓存池大小
private int maxSize;
private LockCachePool(int maxSize, boolean accessOrder) {
super(maxSize, 0.75F, accessOrder);
if(maxSize < 1){
throw new RuntimeException("maxSize不能小于1");
}
this.maxSize = maxSize;
}
public static LockCachePool createLruLockCachePool(int maxSize){
return new LockCachePool(maxSize, true);
}
public static LockCachePool createFifoLockCachePool(int maxSize){
return new LockCachePool(maxSize, false);
}
@Override
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
boolean removeEldestEntry = false;
if(size() > maxSize){
ReentrantLock lock = (ReentrantLock)eldest.getValue();
// 当锁池容量超限,且队头的锁未被持有时,返回true,走模板方法逻辑删除头部节点
if (!lock.isLocked()){
removeEldestEntry = true;
}else {
// 当头部锁被持有时,手动删除最接近头部且未被持有的锁
Iterator<Map.Entry<K, V>> iterator = this.entrySet().iterator();
if(iterator.hasNext()){
// 指向头部锁后的节点
iterator.next();
while (iterator.hasNext() && size() > maxSize){
Map.Entry<K, V> eldestEntry = iterator.next();
ReentrantLock eldestLock = (ReentrantLock) eldestEntry.getValue();
if(!eldestLock.isLocked()){
super.remove(eldestEntry.getKey());
}
}
}
}
}
return removeEldestEntry;
}
}
/**
* 2Q策略锁池类
*/
public static class TwoQuesLockPool{
//LRU锁池大小
@Getter
private int lruMaxSize = 64;
//FIFO锁池大小
@Getter
private int fifoMaxSize = 64;
//LRU锁池
private LockCachePool<String, ReentrantLock> lruLockPool;
//FIFO锁池
private LockCachePool<String, ReentrantLock> fifoLockPool;
//同步锁
private final ReentrantLock mutex = new ReentrantLock();
public TwoQuesLockPool(int fifoMaxSize, int lruMaxSize){
this.fifoMaxSize = fifoMaxSize;
this.lruMaxSize = lruMaxSize;
this.lruLockPool = LockCachePool.createLruLockCachePool(this.lruMaxSize);
this.fifoLockPool = LockCachePool.createFifoLockCachePool(this.fifoMaxSize);
}
public TwoQuesLockPool(){
this.lruLockPool = LockCachePool.createLruLockCachePool(this.lruMaxSize);
this.fifoLockPool = LockCachePool.createFifoLockCachePool(this.fifoMaxSize);
}
/**
* 获取指定锁,默认优先获取LRU缓存中的锁
* @param key 锁名称(控制颗粒度)
* @param isFair 是否公平
* @return
*/
public ReentrantLock getLock(String key, boolean isFair){
ReentrantLock lock = null;
// LRU锁池get会影响排序,需加锁
mutex.lock();
try {
lock = lruLockPool.get(key);
if (lock == null){
lock = fifoLockPool.get(key);
if(lock != null){
lruLockPool.put(key, fifoLockPool.remove(key));
}else {
lock = new ReentrantLock(isFair);
fifoLockPool.put(key, lock);
}
}
}catch (Exception e){
log.error(e.getMessage(), e);
throw e;
}finally {
mutex.unlock();
}
System.out.println(key+"——>fifo——>"+ JSON.toJSONString(fifoLockPool.keySet()));
System.out.println(key+"——>lru——>"+ JSON.toJSONString(lruLockPool.keySet()));
return lock;
}
}
public static void main(String[] args) {
getLock("testkey1",3,3).lock();
getLock("testkey2",3,3);
getLock("testkey3",3,3);
getLock("testkey4",3,3);
getLock("testkey4",3,3);
getLock("testkey3",3,3);
getLock("testkey2",3,3);
getLock("testkey1",3,3);
getLock("testkey1",3,3);
getLock("testkey3",3,3);
}
}
输出:
testkey1——>fifo——>[“testkey1”]
testkey1——>lru——>[]
testkey2——>fifo——>[“testkey1”,“testkey2”]
testkey2——>lru——>[]
testkey3——>fifo——>[“testkey1”,“testkey2”,“testkey3”]
testkey3——>lru——>[]
testkey4——>fifo——>[“testkey1”,“testkey3”,“testkey4”]
testkey4——>lru——>[]
testkey4——>fifo——>[“testkey1”,“testkey3”]
testkey4——>lru——>[“testkey4”]
testkey3——>fifo——>[“testkey1”]
testkey3——>lru——>[“testkey4”,“testkey3”]
testkey2——>fifo——>[“testkey1”,“testkey2”]
testkey2——>lru——>[“testkey4”,“testkey3”]
testkey1——>fifo——>[“testkey2”]
testkey1——>lru——>[“testkey4”,“testkey3”,“testkey1”]
testkey1——>fifo——>[“testkey2”]
testkey1——>lru——>[“testkey4”,“testkey3”,“testkey1”]
testkey3——>fifo——>[“testkey2”]
testkey3——>lru——>[“testkey4”,“testkey1”,“testkey3”]