高效细粒度FIFO-LRU锁池及其封装实现

为了实现更细粒度且有效率的控制ReentrantLock,自定义了一个支持细颗粒度和高效复用的锁池。

细粒度指使用一个自定义的key来动态声明一个或一组具体的资源,这个key值应当是动态的,非写死在代码里的,一旦声明了部分资源的所有权,这个key在锁池中必须是唯一的,而key对应的ReentrantLock对象和Lock对应的资源也当是唯一对应关系,key-lock-资源,一一对应。同时为了应对热点资源的高烈度竞争,避免热点锁对象的频繁创建和销毁,借用了缓存复用的思想,锁池采用了2Q也即FIFO-LRU队列的数据结构。

细颗粒度是通过维护锁池的key-value结构实现的,通过控制key来获得限制不同资源的锁。高效复用这一点是通过让锁池实现2Q缓存淘汰机制实现的。

具体实现是,查找锁时首先查找LRU策略锁池,如果LRU锁池中存在这把锁,则将这把锁移动到LRU锁池队尾,如果不存在则去FIFO策略锁池查找,如果FIFO锁池中存在这把锁,则视为2次命中,将这把锁转移到LRU锁池中,不存在则创建这把锁加入到FIFO锁池中。

FIFO锁池重写了LinkedHashMap的put方法,在添加新锁时检查锁池大小是否超过阈值,超过则淘汰队头未被持有的锁,并把新的锁插入队尾。LRU锁池重写了LinkedHashMap的removeEldestEntry方法,基于LinkedHashMap的模板方法实现在缓存命中时将锁移动到队尾,且在添加新锁时检查锁池大小是否超过阈值,超过则淘汰队头未被持有的锁。

考虑到2Q锁池在访问锁时总是会改变锁(也即一个缓存元素)的排序,所以访问锁的过程本身必须加锁,保证获取锁池行为的串行,这会对性能有一定程度的影响,于是新加入了重排序锁分段的设计,通过按业务类型来区分不同的锁池,保证不同业务的锁池在被访问时不会互相影响吞吐性能。

相比基础的淘汰最久未使用LRU策略,2Q策略一定程度避免了缓存污染问题,缓存命中率更高。具体FIFO和LRU锁池的容量阈值大小取决于业务类型,可以根据自身需求调整以实现更高效率。

业务意义:
相比于分布式锁,JVM级别的锁虽然不能在不同JVM之间保证线程安全,但是依然有存在的意义,例如降低分布式锁的竞争烈度,分布式锁的网络IO毕竟更加耗时,如果能在单一实例中先获得本地锁,然后再去竞争分布式锁的资源,可以大大降低分布式锁的竞争烈度,大大降低网络延迟带来的性能损耗。

2Q淘汰策略示意图:

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
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缓存最大容量
    @Getter
    private static final int LRU_MAX_SIZE = 64;
    // 锁池FIFO缓存最大容量
    @Getter
    private static final int 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<>();

    // 初始化默认锁池
    static {
        POOL_MAP.put(DEFAULT_POOL_NAME, new TwoQuesLockPool(FIFO_MAX_SIZE, LRU_MAX_SIZE));
    }

    /**
     * 获取指定锁
     * @param poolName 锁池名称(控制缓存重排序颗粒度)
     * @param key 锁名称(控制锁颗粒度)
     * @param isFair 是否公平
     * @return
     */
    public static ReentrantLock getLock(String poolName, String key, boolean isFair){
        Assert.notNull(key,"LockUtil.get(String key, boolean isFair) key不能为null");
        return getOrAddLockPool(poolName).getLock(key, isFair);
    }

    /**
     * 获取指定锁
     * @param poolName 锁池名称(控制缓存重排序颗粒度)
     * @param key 锁名称(控制颗粒度)
     * @return
     */
    public static ReentrantLock getLock(String poolName, String key){
        return getLock(poolName, key, false);
    }

    /**
     * 获取指定锁
     * @param key 锁名称(控制颗粒度)
     * @return
     */
    public static ReentrantLock getLock(String key){
        return getLock(DEFAULT_POOL_NAME, key, false);
    }

    /**
     * 获得指定名称的锁池,如果没有就创建
     * @param pooName
     * @return
     */
    private static TwoQuesLockPool getOrAddLockPool(String pooName){
        TwoQuesLockPool lockPool = POOL_MAP.get(pooName);
        // 双检锁的方式检查锁池是否存在
        if(lockPool == null){
            synchronized (pooName.intern()){
                lockPool = POOL_MAP.get(pooName);
                if(lockPool == null){
                    lockPool = new TwoQuesLockPool(FIFO_MAX_SIZE, LRU_MAX_SIZE);
                    POOL_MAP.put(pooName, lockPool);
                }
            }
        }
        return lockPool;
    }

    /**
     * 支持LRU算法的锁池类
     * @param <K>
     * @param <V>
     */
    @Getter
    private static class LRULockPool<K,V> extends LinkedHashMap<K,V> {
        // 触发回收的缓存池大小,实际可能大于
        private int maxSize;

        public LRULockPool(int maxSize) {
            super(maxSize, 0.75F, true);
            if(maxSize < 1){
                throw new RuntimeException("maxSize不能小于1");
            }
            this.maxSize = maxSize;
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
            boolean removeEldestEntry = false;
            if(size() > maxSize){
                ReentrantLock lock = (ReentrantLock)eldest.getValue();
                // 当LRU锁池容量超限,且队头的锁未被持有时,返回true,走模板方法逻辑删除头部节点
                if (!lock.isLocked()){
                    removeEldestEntry = true;
                }else {
                    // 当头部锁被持有时,手动删除最接近头部且未被持有的锁
                    Iterator<Map.Entry<K, V>> iterator = this.entrySet().iterator();
                    if(iterator.hasNext()){
                        // 指向头部锁后的节点
                        iterator.next();
                        while (iterator.hasNext()){
                            Map.Entry<K, V> eldestEntry = iterator.next();
                            ReentrantLock eldestLock = (ReentrantLock) eldestEntry.getValue();
                            if(!eldestLock.isLocked()){
                                super.remove(eldestEntry.getKey());
                                break;
                            }
                        }
                    }
                }
            }
            return removeEldestEntry;
        }
    }

    /**
     * 支持FIFO策略的锁池类
     * @param <K>
     * @param <V>
     */
    @Getter
    private static class FIFOLockPool<K,V> extends LinkedHashMap<K,V> {
        // 触发回收的缓存池大小,实际可能大于
        private int maxSize;

        public FIFOLockPool(int maxSize) {
            super(maxSize, 0.75F, true);
            if(maxSize < 1){
                throw new RuntimeException("maxSize不能小于1");
            }
            this.maxSize = maxSize;
        }

        @Override
        public V put(K key, V value) {
            V put = super.put(key, value);
            if(size() > maxSize){
                // 当锁池达到最大容量,清除最靠近头部(最近最少使用)且未被持有的锁
                Iterator<Map.Entry<K, V>> iterator = this.entrySet().iterator();
                while (iterator.hasNext()){
                    Map.Entry<K, V> eldestEntry = iterator.next();
                    ReentrantLock eldestLock = (ReentrantLock) eldestEntry.getValue();
                    if(!eldestLock.isLocked()){
                        super.remove(eldestEntry.getKey());
                        break;
                    }
                }
            }
            return put;
        }
    }

    /**
     * 2Q策略锁池类
     */
    public static class TwoQuesLockPool{
        //LRU锁池大小
        @Getter
        private int lruMaxSize = 64;
        //FIFO锁池大小
        @Getter
        private int fifoMaxSize = 64;
        //LRU锁池
        private LRULockPool<String,ReentrantLock> lruLockPool;
        //FIFO锁池
        private FIFOLockPool<String,ReentrantLock> fifoLockPool;
        //同步锁
        private final ReentrantLock mutex = new ReentrantLock();

        public TwoQuesLockPool(int fifoMaxSize, int lruMaxSize){
            this.fifoMaxSize = fifoMaxSize;
            this.lruMaxSize = lruMaxSize;
            this.lruLockPool = new LRULockPool<>(this.lruMaxSize);
            this.fifoLockPool = new FIFOLockPool<>(this.fifoMaxSize);
        }
        public TwoQuesLockPool(){
            this.lruLockPool = new LRULockPool<>(this.lruMaxSize);
            this.fifoLockPool = new FIFOLockPool<>(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();
            }
            return lock;
        }
    }
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是基于 C++ 的 OPT, FIFO, LRU 页面置换算法的实现代码: ```c++ #include <iostream> #include <vector> #include <queue> #include <algorithm> #include <unordered_map> using namespace std; // OPT 页面置换算法 int OPT(vector<int> pages, int frameSize) { int pageFaults = 0; unordered_map<int, int> nextUse; vector<int> frames(frameSize, -1); for(int i = 0; i < pages.size(); i++) { int page = pages[i]; if(find(frames.begin(), frames.end(), page) != frames.end()) { continue; } if(frames[frames.size() - 1] == -1) { frames[pageFaults] = page; pageFaults++; } else { for(int j = 0; j < frames.size(); j++) { int frame = frames[j]; if(nextUse.find(frame) == nextUse.end()) { nextUse[frame] = i; } else { if(nextUse[frame] < nextUse[frames[j]]) { nextUse[frame] = i; } } } int maxNextUse = -1; int pageToReplace = -1; for(int j = 0; j < frames.size(); j++) { int frame = frames[j]; if(nextUse[frame] > maxNextUse) { maxNextUse = nextUse[frame]; pageToReplace = frame; } } replace(frames.begin(), frames.end(), pageToReplace, page); pageFaults++; } } return pageFaults; } // FIFO 页面置换算法 int FIFO(vector<int> pages, int frameSize) { int pageFaults = 0; queue<int> frames; for(int i = 0; i < pages.size(); i++) { int page = pages[i]; if(find(frames.front(), frames.back(), page) != frames.back()) { if(frames.size() == frameSize) { frames.pop(); } frames.push(page); pageFaults++; } } return pageFaults; } // LRU 页面置换算法 int LRU(vector<int> pages, int frameSize) { int pageFaults = 0; unordered_map<int, int> lastUse; vector<int> frames(frameSize, -1); for(int i = 0; i < pages.size(); i++) { int page = pages[i]; if(find(frames.begin(), frames.end(), page) != frames.end()) { lastUse[page] = i; continue; } if(frames[frames.size() - 1] == -1) { frames[pageFaults] = page; lastUse[page] = i; pageFaults++; } else { int minLastUse = INT_MAX; int pageToReplace = -1; for(int j = 0; j < frames.size(); j++) { int frame = frames[j]; if(lastUse[frame] < minLastUse) { minLastUse = lastUse[frame]; pageToReplace = frame; } } replace(frames.begin(), frames.end(), pageToReplace, page); lastUse[page] = i; pageFaults++; } } return pageFaults; } int main() { vector<int> pages = {1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5}; int frameSize = 3; cout << "OPT 页面置换算法: " << OPT(pages, frameSize) << endl; cout << "FIFO 页面置换算法: " << FIFO(pages, frameSize) << endl; cout << "LRU 页面置换算法: " << LRU(pages, frameSize) << endl; return 0; } ``` 在这个代码中,我们定义了三个函数 `OPT`, `FIFO`, 和 `LRU`,分别实现了 OPT, FIFO, 和 LRU 页面置换算法。每个函数都接受一个整数数组 `pages` 和一个整数 `frameSize`,其中 `pages` 表示页面序列,`frameSize` 表示内存中页面框的数量。每个函数都返回页面缺页次数。 在 `OPT` 函数中,我们使用了一个哈希表 `nextUse` 来记录每个页面下一次使用的位置。当需要替换页面时,我们选择下一次使用最远的页面进行替换。 在 `FIFO` 函数中,我们使用了一个队列来记录页面。当需要替换页面时,我们选择队列中最早进入的页面进行替换。 在 `LRU` 函数中,我们使用了一个哈希表 `lastUse` 来记录每个页面上一次使用的位置。当需要替换页面时,我们选择上一次使用时间最早的页面进行替换。 注意,以上实现代码仅供参考。实际使用中,还需要考虑到多种情况,比如页面访问的顺序、页面分布的情况等等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值