Cache+多线程下内存失效的一个bug

本文讨论了一个在多线程环境下使用缓存技术时遇到的问题,即缓存在过期时间到期后变为null,进而导致后续操作中出现空指针引用错误。通过分析代码,解释了问题发生的可能性及其罕见性,并提供了理解问题的背景知识。
public static class MessageType2TemplateService
    {
        private static Dictionary<int, HtmlTemplateModel> list = new Dictionary<int, HtmlTemplateModel>();
        private static HtmlTemplateDAL dao = new HtmlTemplateDAL();

        public static HtmlTemplateModel getTemplate(int messageType)
        {
            try
            {
                StopWatcher sw = new StopWatcher();
                
                lock (list)
                {
                    if (HttpRuntime.Cache["HtmlTemplateListHashByMessageType"] == null)
                    {
                        sw.start();
                        list.Clear();
                        HtmlTemplateModel model=new HtmlTemplateModel();
                        model.PageSize=int.MaxValue;
                        model.CurPage=1;
                        IList<HtmlTemplateModel> templateList = dao.GetHtmlTemplateList(model);
                        if (templateList.Count > 0)
                        {
                            foreach (HtmlTemplateModel item in templateList)
                            {
                                if (item.MessageType == null)
                                {
                                    continue;
                                }
                                item.TemplateHtml = string.Empty;
                                list.Add((int)item.MessageType, item);
                            }
                        }
                        HttpRuntime.Cache.Add("HtmlTemplateListHashByMessageType", list, null, DateTime.Now.AddMinutes(20), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
                        sw.stop();
                        EdmLogger.LogProcess("获取所有模板", sw.getTime());
                    }
                    else
                    {
                        list = (Dictionary<int, HtmlTemplateModel>)HttpRuntime.Cache["HtmlTemplateListHashByMessageType"];
                    }

                }

                if (list.ContainsKey(messageType))
                {
                    return list[messageType];
                }
                else
                {
                    throw new Exception("没有messageType对应的模板");
                }

            }
            catch (Exception ex)
            {
                EdmLogger.LogError("根据messageType获取模板错误", ex);
                throw ex;
            }
        }
    }


这里是在多线程下的一个静态类
用于根据messageType获取模板
然后这些模板会保存在cache里面
同样的模板会保存两份
一份在cache一份在Dictionary里面
其实在这个场景里面cache只起到了定时器的作用,只是因为沿用以前的规范用了cache
然后这个周末就爆出了一个bug
list变成null然后程序报空指针引用的bug
拿到程序分析了下
确实存在这样一个bug:

lock(list)判断缓存是否为空的时候缓存不为空
但是当到else里面让list引用缓存的时候缓存却因为设定的过期时间到期变为null
于是list就变为了null
于是下一次调用的时候在lock(list)的时候就会报出空指针引用的错误

当然这种情况很少见,只有极其微小的概率
因为必须要时间刚好卡在过期的时间前的一瞬间在处理那个if判断
这个几率不大于百万分之一

但是上周末确实就出现了...

关于 Spring Cache 的已知问题和 Bug 列表,虽然未提供具体引用支持,但可以基于常见场景和技术文档总结一些典型问题及其解决方式。 ### 常见的 Spring Cache 已知问题 #### 1. 缓存穿透 缓存穿透是指查询一个不存在的数据时,由于缓存中没有对应的键值对,每次请求都会直接打到数据库,造成数据库压力。 解决方案通常是在缓存中存储空对象或者使用布隆过滤器来判断数据是否存在[^5]。 ```java @Cacheable(value = "items", key = "#id", unless = "#result == null") public Item getItemById(String id) { return itemRepository.findById(id); } ``` #### 2. 缓存击穿 缓存击穿指的是某个热点数据失效时,大量并发请求同时到达数据库,导致数据库负载过高。可以通过加锁机制或设置永不过期的方式来缓解此问题[^6]。 ```java @CachePut(value = "hotItems", key = "#item.id") public Item updateHotItem(Item item) { return itemRepository.save(item); } ``` #### 3. 缓存雪崩 当缓存中的多个数据在同一时间过期,可能导致大量请求瞬间落到数据库上,引发雪崩效应。通过为不同缓存项设置随机过期时间和预热策略可以有效避免这一现象[^7]。 ```properties spring.cache.redis.time-to-live=PT10S # 设置 Redis TTL 时间 ``` #### 4. 并发更新问题 在高并发环境下,如果多个线程尝试更新同一个缓存项,可能会导致数据不一致或丢失更新的情况发生。此时应引入分布式锁或其他同步手段加以控制[^8]。 ```java @Component public class DistributedLockService { @Autowired private StringRedisTemplate redisTemplate; public boolean tryLock(String lockKey, long timeoutMs) { ValueOperations<String, String> ops = redisTemplate.opsForValue(); Boolean success = ops.setIfAbsent(lockKey, "LOCKED", Duration.ofMillis(timeoutMs)); return Boolean.TRUE.equals(success); } public void releaseLock(String lockKey) { redisTemplate.delete(lockKey); } } ``` --- ### 总结 上述问题是 Spring Cache 使用过程中较为常见的挑战,并附带了一些基本应对措施。需要注意的是实际开发环境中还需结合业务需求进一步优化配置与设计模式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值