首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >今天不聊技术,发两道题.为什么?年底了,做完可能有惊喜哦

今天不聊技术,发两道题.为什么?年底了,做完可能有惊喜哦

作者头像
烟雨平生
发布2026-04-14 18:31:39
发布2026-04-14 18:31:39
240
举报

生活就是一道道待解决的题,工作中的任务也是一道道待解决的题。

考查你能不能胜任这份工作,也是一道道题。

回到正题,题如下:

1、令牌桶。限流场景常见

设计并实现一个令牌桶。初始化时,需要初始化令牌桶的 最大容量和每秒生成令牌的数量。

通过tryAcquire(int tokens)来获取令牌,且支持并发调用。不能用额外的线程来维护令牌桶中的令牌数。

2、LRU缓存。Redis Key过期场景常见

设计并实现一个LRU缓存,要支持并发,不能使用ConcurrentHashMap、LinkedHashMap,get时,不存在则报错,put时插入或update,如果超过最大缓存容量,则把长时间不使用的key清除。

先想一下。

觉得有想法了

参考答案:

1、令牌桶。

思路:

1.1 只用一个操作入口get方法,那么可以使用CAS来增加并发数。

1.2 不能使用额外线程来维护Bucket中的token数,那这个判断和补token数的逻辑只能在get方法中。

代码语言:javascript
复制
import java.util.concurrent.atomic.AtomicReference;
/**
 * 题目1:令牌桶
 * 要求:初始化最大容量和每秒生成数量。
 * tryAcquire(int tokens) 支持并发,不使用额外线程维护。
 * 
 * 改进:使用 CAS (AtomicReference) 替代 synchronized,实现无锁高并发。
 */
public class TokenBucket {
    private final long maxCapacity;
    private final double ratePerMillisecond;

    // 使用 AtomicReference 原子更新状态,避免锁竞争
    private final AtomicReference<BucketState> state;
    // 内部不可变状态类
    private static class BucketState {
        final double currentTokens;
        final long lastRefillTimestamp;
        BucketState(double currentTokens, long lastRefillTimestamp) {
            this.currentTokens = currentTokens;
            this.lastRefillTimestamp = lastRefillTimestamp;
        }
    }
    /**
     * @param maxCapacity 最大容量
     * @param tokensPerSecond 每秒生成的令牌数量
     */
    public TokenBucket(long maxCapacity, long tokensPerSecond) {
        if (maxCapacity <= 0 || tokensPerSecond <= 0) {
            throw new IllegalArgumentException("Capacity and rate must be positive");
        }
        this.maxCapacity = maxCapacity;
        // 将每秒的速率转换为每毫秒的速率
        this.ratePerMillisecond = (double) tokensPerSecond / 1000.0;

        // 初始化状态
        this.state = new AtomicReference<>(new BucketState(maxCapacity, System.currentTimeMillis()));
    }
    /**
     * 尝试获取令牌 (CAS 无锁实现)
     * @param tokens 需要获取的令牌数
     * @return 是否获取成功
     */
    public boolean tryAcquire(int tokens) {
        if (tokens <= 0) {
            return false;
        }
        while (true) {
            BucketState currentState = state.get();
            long now = System.currentTimeMillis();

            // 计算重填后的令牌数
            double newTokens = currentState.currentTokens;
            if (now > currentState.lastRefillTimestamp) {
                long timeElapsed = now - currentState.lastRefillTimestamp;
                double tokensToAdd = timeElapsed * ratePerMillisecond;
                newTokens = Math.min(maxCapacity, newTokens + tokensToAdd);
            } else {
                // 如果时间倒流或极短时间内调用,时间戳保持不变
                now = currentState.lastRefillTimestamp;
            }
            // 检查令牌是否足够
            if (newTokens < tokens) {
                return false;
            }
            // 构造新状态:扣减令牌,更新时间戳
            BucketState newState = new BucketState(newTokens - tokens, now);
            // CAS 更新,如果成功则返回 true,失败则循环重试
            if (state.compareAndSet(currentState, newState)) {
                return true;
            }
        }
    }

}

2、LRU缓存。

思路:并发通过ReentrantLock来实现get和put之间的同步操作,确保线程安全。

代码语言:javascript
复制
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 题目2:LRU缓存
 * 要求:支持并发,不能使用ConcurrentHashMap、LinkedHashmap。
 * 
 * 实现:HashMap + 双向链表 + 全局锁 (ReentrantLock)
 * 这种实现简单清晰,完全满足线程安全要求。
 */
public class LRUCache<K, V> {

    // 双向链表节点
    private static class Node<K, V> {
        K key;
        V value;
        Node<K, V> prev;
        Node<K, V> next;
        Node(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }
    private final int capacity;
    private final Map<K, Node<K, V>> map;

    // 伪头节点和伪尾节点,简化链表操作
    private final Node<K, V> head;
    private final Node<K, V> tail;

    // 全局锁,保证线程安全
    private final ReentrantLock lock = new ReentrantLock();
    public LRUCache(int capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException("Capacity must be positive");
        }
        this.capacity = capacity;
        this.map = new HashMap<>(capacity);

        // 初始化双向链表
        this.head = new Node<>(null, null);
        this.tail = new Node<>(null, null);
        head.next = tail;
        tail.prev = head;
    }
    /**
     * 获取值
     * 1. 加锁
     * 2. 从 Map 获取节点
     * 3. 如果存在,移动到链表头部(表示最近使用)
     * 4. 解锁
     */
    public V get(K key) {
        lock.lock();
        try {
            Node<K, V> node = map.get(key);
            if (node == null) {
                throw new RuntimeException("Key not found: " + key);
            }
            moveToHead(node);
            return node.value;
        } finally {
            lock.unlock();
        }
    }
    /**
     * 插入或更新
     * 1. 加锁
     * 2. 检查 Key 是否存在
     * 3. 存在 -> 更新值,移动到头部
     * 4. 不存在 -> 创建新节点,加入 Map 和链表头部
     * 5. 检查容量 -> 超过则移除尾部节点(最近最少使用)
     * 6. 解锁
     */
    public void put(K key, V value) {
        lock.lock();
        try {
            Node<K, V> node = map.get(key);

            if (node != null) {
                // 更新值并移动到头部
                node.value = value;
                moveToHead(node);
            } else {
                // 新增节点
                Node<K, V> newNode = new Node<>(key, value);
                map.put(key, newNode);
                addToHead(newNode);
                // 超过容量,移除最近最少使用(尾部的前一个)
                if (map.size() > capacity) {
                    removeLRU();
                }
            }
        } finally {
            lock.unlock();
        }
    }

    // --- 链表辅助方法 (必须在锁内调用) ---
    private void moveToHead(Node<K, V> node) {
        removeNode(node);
        addToHead(node);
    }
    // 插入到头节点之后
    private void addToHead(Node<K, V> node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }
    // 从链表中移除节点
    private void removeNode(Node<K, V> node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
    // 移除最近最少使用的节点(Tail 的前一个)
    private void removeLRU() {
        Node<K, V> lruNode = tail.prev;
        if (lruNode != head) {
            removeNode(lruNode);
            map.remove(lruNode.key);
        }
    }

}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 的数字化之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、令牌桶。限流场景常见
  • 1、令牌桶。
  • 2、LRU缓存。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档