总前置判断口诀(先记这句)
多线程共用静态 / 实例共享变量,不加 volatile、不加锁、不做局部快照拷贝 → 100% 存在线程不安全 + 指令重排风险
一、一眼识别【指令重排高危特征代码】(8 条,高频模板)
只要出现下面任意一种代码形态,直接判定存在重排漏洞
1. 共享引用分步初始化(DCL 同款重排)
特征:static 引用先 new 对象,再给对象内部字段赋值
static Data data;
void init(){
data = new Data();
data.x = 10; // 重排:引用先暴露,字段未赋值
}
识别标记:共享引用 = new 对象 + 连续对象属性赋值,两行分开写
2. 布尔标记变量,线程循环判断,无 volatile
特征:static boolean 做启停标记,子线程 while 循环判断
static boolean stop = false;
void run(){
while(!stop){}
}
识别标记:全局布尔标记 + 死循环读取,无 volatile 修饰
3. 两个独立共享变量,先写 A 再写 B,另一个线程先读 B 再读 A
static int a;
static boolean flag;
void write(){
a = 1;
flag = true;
}
void read(){
if(flag) print(a);
}
识别标记:两个无 volatile 共享变量,一写两段赋值,一读条件判断
4. 多全局数值参与计算,直接在线读取,不拷贝局部快照
static int a,b,c;
int calc(){
int r1 = a*b;
int r2 = b*c;
return r1+r2;
}
识别标记:多处重复读取 static 变量,没有统一先拷贝到局部变量
5. 锁外读写共享变量,临界区只包裹部分逻辑
static int num;
void op(){
num = 10; // 锁外写共享变量,会和锁内代码重排
synchronized(this){
// 业务
}
}
识别标记:同步块前后存在共享变量读写
6. 异步线程、CompletableFuture 直接捕获外部 static 变量
static long flag;
void async(){
flag = 99;
CompletableFuture.runAsync(()->print(flag));
}
识别标记:主线程修改全局变量,立刻丢给异步线程读取
7. 数组 / 集合全局引用,分步赋值内部元素
static int[] arr = new int[2];
void set(){
arr[0] = 1;
arr[1] = 2;
}
识别标记:全局数组,逐下标赋值,无同步 /volatile
8. new 对象作为返回值,直接 return 分步构造
static User user;
User getUser(){
User u = new User();
u.id = 1;
u.name = "test";
return u;
}
识别标记:局部对象分步赋值后直接暴露给共享 / 返回
二、一眼识别【线程不安全通用特征】(12 条,覆盖并发所有坑)
1. 共享变量类(最容易出现)
- Service 单例实例变量(非 static 也危险)
@Service
public class BizService{
private List<Order> tempList; // 实例成员,多线程共用
}
识别标记:Spring 单例 Bean 内定义集合、包装类、DTO 成员变量
- static 全局容器(List/Map),无并发类、无读写锁
static List<String> cache = new ArrayList<>();
标记:static + 普通集合,直接 add/remove
- ThreadLocal 使用完无 finally remove
ThreadLocal<User> tl = new ThreadLocal<>();
void handle(){
tl.set(user);
// 无finally清理
}
标记:set 之后没有 try-finally 包裹 remove
2. 复合操作无原子性
- 共享变量自增 i++、+=、-=
static int count = 0;
void add(){ count++; }
标记:共享变量自增 / 复合赋值,无 Atomic、无锁
- 先 get 再 set,两步操作修改共享缓存
Integer num = map.get(id);
map.put(id, num+1);
标记:查询 + 修改分两行,ConcurrentHashMap 也不安全
3. 可见性问题
-
共享数字 / 布尔无 volatile,多线程读写标记:static / 实例基础类型变量,多线程同时读写,没 volatile
-
循环读取全局变量,无任何同步措施标记:while/for 循环内反复读取实例 /static 变量
4. 集合遍历 / 修改
-
ArrayList/HashMap 多线程同时 add、remove、遍历标记:普通集合,多线程写,无 CopyOnWrite、无锁
-
增强 for 循环遍历同时 remove 元素
for(String s : list){
list.remove(s);
}
标记:增强 for + 集合删除,必抛并发修改异常
5. 异步 / 流式
- parallelStream 外部普通变量累加
AtomicInteger sum = new AtomicInteger();
list.parallelStream().forEach(sum::addAndGet);
标记:并行流操作外部可变变量(非原子也危险)
- 匿名内部类 Runnable 持有外部 this 大对象,常驻线程
new Thread(new Runnable(){
@Override
public void run(){
while(true){} // 持有外部Service引用
}
}).start();
标记:无限循环子线程 + 匿名内部类
- 多线程复用同一个 Wrapper、Page 对象
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 多线程共用同一个wrapper查询
标记:非线程安全工具类全局复用
三、快速排查三步法(编码审查自查流程,30 秒定位)
第一步:找出所有「多线程可同时访问」的变量
筛选三类高危变量:
- static 静态变量(全局所有线程共享)
- Spring 单例 Service/Controller 的实例成员变量
- ThreadLocal、全局缓存集合、异步捕获的外部变量
第二步:判断操作是否存在「多步拆分读写」
满足任意一条 = 重排风险:
- 先写变量 A,再写变量 B;
- 先读变量 A,再读变量 B;
- 对象引用先赋值,再填充内部字段;
- 同一共享变量多处分开读取(计算重复读取);
- 写操作后立刻交给异步 / 子线程读取。
第三步:检查是否有「屏障 / 快照 / 锁」防护
无下面任意一种修复手段 → 确定漏洞:
- 变量加 volatile;
- 全部读写被 synchronized/Lock 包裹;
- 读取前统一拷贝到局部变量(快照隔离);
- 使用原子类 AtomicInteger/AtomicReference;
- 读写分离,不共享可变对象。
四、极简记忆口诀(背诵版,快速复盘)
重排识别口诀
静态引用分步赋值,标记循环无 volatile;两变量先后读写,计算重复读全局;锁内外读写共享值,异步捕获全局变量;数组逐一分段赋值,全是重排高危代码。
线程不安全识别口诀
单例成员存集合,static 容器无并发;自增复合两步改,本地线程忘清除;普通集合多线程写,增强 for 里删元素;并行流改外部变量,匿名线程持 this;全局包装循环读,无锁无 volatile 必出事。
五、无风险安全代码对照(反向记忆)
- 共享变量统一拷贝局部再计算(快照隔离,防重排)
- 全局布尔标记加 volatile
- 对象先局部完整初始化,最后赋值共享引用
- 多线程修改使用 Atomic 原子类
- 读写共享变量统一加锁包裹完整逻辑
- ThreadLocal try-finally 强制 remove
- 单例不定义可变实例集合,临时 List 全部方法内新建
335

被折叠的 条评论
为什么被折叠?



