并发判空核心是“不假设、不信任、不跳过”:需识别可变共享状态,分层判空集合链,用volatile+双重检查安全初始化,优先采用不可变容器与Optional等空感知类型。

并发类内元素的判空逻辑,核心在于“不假设、不信任、不跳过”——尤其在多线程环境下,对象可能被其他线程置为null、尚未完成初始化,或正处于状态切换中。高频空指针往往不是因为没写if,而是判空时机错、范围窄、或忽略了并发语义。
先确认元素是否属于“可变共享状态”
若该元素是类成员且被多个线程读写(如private volatile List<string> cacheList</string>),它就属于高风险目标。此时不能只判cacheList != null,还要考虑:
- 它可能刚被清空但引用未置
null,需配合isEmpty() - 它可能是懒加载的,首次访问时由某线程初始化,但其他线程已抢先执行到取值逻辑
- 它被
ConcurrentHashMap等容器持有,但容器本身不保证其value非空
对集合/数组类元素,必须分层判空
例如访问user.getOrders().get(0).getAmount(),这是典型的三连空风险链。正确做法是逐层防御,而非仅检查首层:
if (user != null && user.getOrders() != null && !user.getOrders().isEmpty())- 再取
Order order = user.getOrders().get(0)前,加if (order != null) - 最后调用
order.getAmount()才安全
更推荐封装成工具方法:SafeAccessor.firstOrNull(user.getOrders()).map(Order::getAmount).orElse(null),借助Optional链式表达,语义清晰且线程安全(前提是getOrders()返回不可变或线程安全集合)。
使用volatile + double-check + 初始化屏障
对延迟初始化的内部元素(如缓存、连接池),避免“this逃逸”和部分构造问题:
- 声明为
private volatile Map<string data> internalCache</string> - 初始化时用双重校验锁,并确保构造完成后再赋值给volatile字段
- 访问时仍要判
if (internalCache != null),因volatile不保证后续操作原子性
示例关键片段:
public Data getData(String key) {if (internalCache == null) {
synchronized (this) {
if (internalCache == null) {
internalCache = new ConcurrentHashMap(); // 完整构造后才赋值
}
}
}
return internalCache.get(key); // 此处get返回null是业务语义,非NPE风险
}
优先用不可变容器与空感知类型
从源头降低风险:
- 用
ImmutableList.of()代替new ArrayList(),避免外部修改导致意外null元素 - 集合元素若允许
null,改用Optional<T>包装,如List<Optional<User>>,强制调用方处理空情况 - 数据库查询结果映射时,用MyBatis-Plus的
@TableField(fill = FieldFill.INSERT)或自定义TypeHandler统一转空为默认值


















