Tomcat缓存源码CachedResource的getLastModified方法写法疑问
关于
CachedResource.getLastModified()两种写法的差异说明 直接操作成员变量的写法存在并发安全问题
你改写的第二种实现,在多线程并发场景下会随机抛出空指针异常,核心原因是代码会两次读取堆上的成员变量cachedLastModified,两次读取之间没有任何一致性保证:
- 第一次读取发生在
if (this.cachedLastModified == null)判空逻辑中 - 第二次读取发生在
return this.cachedLastModified.longValue()返回逻辑中
CachedResource本身带缓存失效机制,缓存到期时后台清理线程会把cachedLastModified重置为null触发下次重载;同时这个方法没有加任何同步锁,cachedLastModified字段也没有用volatile修饰,两次读取之间完全可能被其他线程修改值:如果第一次判空时读到非null值,跳过了赋值分支,结果返回前第二次读取刚好读到清理线程设置的null值,对null调用longValue()就会直接触发NPE。
Tomcat采用局部变量缓存写法的设计考量
Tomcat的写法本质是Java高并发无锁编码中非常经典的局部变量快照技巧,核心考量有两点:
- 彻底避免空指针风险:代码只在方法最开始读取一次成员变量,把引用拷贝到线程私有的栈上局部变量中,后续所有判空、赋值、返回操作都基于这个局部变量完成。不管其他线程怎么修改堆上的成员变量,当前线程持有的局部变量引用都不会变,只要第一次读取拿到非null值,后续调用
longValue()就绝对不会触发NPE。 - 性能最优:这种实现完全不需要加锁,也不需要给字段加
volatile带来内存屏障开销,同时局部变量的栈读写速度远快于堆成员变量读写。Tomcat刻意允许极端场景下多个线程同时判空、重复给cachedLastModified赋值——因为webResource.getLastModified()的返回值是幂等的,重复赋值不会产生业务错误,用极低概率的重复计算换无锁的高吞吐,是Web容器这类基础组件非常典型的工程取舍。
内容的提问来源于stack exchange,提问作者S. Pan
相关产品推荐
相关产品推荐

