You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Caffeine LoadingCache中安全存储ByteBuf以避免竞态条件?

如何在Caffeine LoadingCache中安全存储ByteBuf以避免竞态条件?

这个问题我之前也踩过坑——Netty的ByteBuf靠引用计数管理生命周期,而Caffeine的异步驱逐机制刚好会在你拿到buf但还没来得及retain的间隙,把buf给释放了,直接导致手里的实例变成无效状态。

给你两个可行的解决方案,其中第一个更稳妥:

方案一:用Netty的retainIfAccessible()原子性保住有效引用

Netty专门提供了retainIfAccessible()方法,它会尝试增加引用计数,但只有当buf还处于可用状态(引用计数大于0)时才会成功。我们可以利用这个特性来规避竞态:

ByteBuf buffer;
while (true) {
    buffer = cache.get("hello");
    // 尝试原子性retain,成功说明buf还活着
    if (buffer.retainIfAccessible()) {
        break;
    }
    // 失败的话,说明buf已经被释放了,赶紧把这个无效的缓存条目清掉,重新加载
    cache.invalidate("hello");
}
// 现在可以放心用buffer了
// 用完别忘了调用buffer.release()释放自己的引用

这个逻辑的关键是:哪怕缓存刚好在你get完buf之后就触发了驱逐,retainIfAccessible()也能检测到buf已经失效,这时候我们主动失效缓存,让下一次get重新生成一个新的ByteBuf,直到成功拿到可用的实例并完成retain。

方案二:调整缓存内ByteBuf的引用计数生命周期(有局限性)

你也可以让缓存本身持有ByteBuf的一个引用,确保缓存持有期间buf的引用计数至少为1,这样即使被驱逐,也不会直接把计数降到0:

  1. 加载时先给缓存留个引用:在CacheLoader里创建完ByteBuf后,先调用一次retain(),让缓存持有这个buf的一个引用:
LoadingCache<String, ByteBuf> cache = Caffeine.newBuilder()
    .maximumSize(128)
    .evictionListener((key, buf, cause) -> buf.release()) // 缓存驱逐时释放自己持有的引用
    .build(key -> {
        ByteBuf byteBuf = createByteBufFromPool(); // 从对象池创建buf
        byteBuf.retain(); // 缓存占一个引用位
        return byteBuf;
    });
  1. 自己拿的时候再占一个引用:从缓存拿到buf后,立刻调用retain():
ByteBuf buffer = cache.get("hello").retain();
// 使用buffer...
buffer.release(); // 用完释放自己的引用

不过要注意,这个方案还是有小概率的竞态:如果get返回buf后,缓存立刻触发驱逐,listener的release()会把计数从1降到0,这时候你再调用retain()还是会失败。所以这个方案只能降低概率,不能完全避免问题,还是方案一更可靠。

备注:内容来源于stack exchange,提问作者James Goodall

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.21 12:18:04