suloser
V2EX  ›  问与答

如何分析这段线程安全的代码?

  •  
  •   suloser · 21h 52m ago · 1123 views

    最近看了一道面试题(先查缓存,缓存中没有再查数据库),其实很久以前就遇到过,我以为已经掌握了,
    但是今天再看,分析起来总觉得有些吃力,有种拿着答案分析过程的感觉,没有真正理解。
    根据我的经验,应该在大脑中形成一个足够抽象的结构图,下次遇到这类问题,都可以套用。
    想问问 v 友们如何分析这类问题?思维过程是怎样的?会在大脑中构建图像吗?

    private final Map<Long, Shop> shopCache = new ConcurrentHashMap<>();
    
    public Shop getShopById(Long shopId) {
        // 1. 先查本地缓存
        Shop shop = shopCache.get(shopId);
        if (shop != null) {
            return shop;
        }
    
        // 2. 同一个 shopId 加锁
        synchronized (getLock(shopId)) {
    
            // 3. 双重检查
            shop = shopCache.get(shopId);
            if (shop != null) {
                return shop;
            }
    
            // 4. 缓存没有,查数据库
            shop = shopMapper.selectById(shopId);
    
            // 5. 放入本地缓存
            if (shop != null) {
                shopCache.put(shopId, shop);
            }
    
            return shop;
        }
    }
    
    private final Map<Long, Object> locks = new ConcurrentHashMap<>();
    
    private Object getLock(Long shopId) {
        return locks.computeIfAbsent(shopId, k -> new Object());
    } 
    
    8 replies    2026-09-11 11:35:54 +08:00
    cloudzhou
        1
    cloudzhou  
       21h 45m ago
    你先理解这道题目的本质
    如果只是 get cache -> check -> db -> put cache 那么逻辑非常简单

    难度是 synchronized getLock 的使用,是为了解决什么问题呢?
    1. 同一个商品 shopId 应该使用同一个锁
    2. 如果已经有一个线程在进行补充 cache 的动作了,其他线程等待,do once 的动作

    1 、2 结合起来,需要一个 synchronized getLock
    同时因为并发原因,需要一个 double check
    suloser
        2
    suloser  
    OP
       21h 14m ago
    @cloudzhou #1 你这个分析是对的,其实这段代码是一个引子,我更想知道,大家遇到这类问题时大脑怎么分析?思考过程是什么样?假如换成其他的并发问题如何分析?是否有一套可以复用的思维结构?
    SachinBeyond
        3
    SachinBeyond  
       20h 24m ago via Android
    @cloudzhou 有两个问题我想问下
    1 ,需要一个 synchronized getLock 是啥意思? getlock 需要 加 synchronized 关键字?
    2 ,楼主代码给了这么多,这个和 shopeCache 直接 computeifabsent 有什么区别?直接 compute ifabsent 不就可以了吗?
    MrZhaoyx
        4
    MrZhaoyx  
       19h 18m ago
    @SachinBeyond
    1. synchronized 不是对 getLock 加锁,是使用 getLock 获取的对象作为锁,对 synchronized 内部的代码块加锁。
    2. 我猜是防止缓存击穿,如果缓存不存在或者过期,同一时间大量请求获取该过期的 shop ,大量请求在同一时间穿透到数据库。
    yidinghe
        5
    yidinghe  
    PRO
       18h 57m ago via Android
    shopCache 本身就可以调用 computeIfAbsent 方法,何必多此一举。
    smallest10
        6
    smallest10  
       17h 15m ago
    确实 shopCache.computeIfAbsent 就解决问题了。

    单说怎么写并发安全代码的话,如果手很生么,那就先写正常业务逻辑代码,然后找其中的并发问题点,然后加锁解决并发安全问题。加锁的时候,你就会发现锁附近有些代码有性能优化空间,顺便再优化优化。就完了。
    cloudzhou
        7
    cloudzhou  
       7h 19m ago
    @SachinBeyond 是可以,这个代码有点像 computeIfAbsent 的实现一样,倒是可以作为一个面试题

    @suloser 先理解并发的本质,Java concurrency in practice 这本书就很好
    sakasaka
        8
    sakasaka  
       5h 22m ago
    脑海里要能构建出多线程并发的画面
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   5404 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 08:58 · PVG 16:58 · LAX 01:58 · JFK 04:58
    ♥ Do have faith in what you're doing.