智享教程网
白蓝主题五 · 清爽阅读
首页  > 生活问答

线程安全怎么实现?几个实用办法,程序员天天在用

你写了个抢红包程序,结果同一秒被点几十次,红包发重了;或者做个库存系统,两个用户同时下单,库存从10变成-1……这些都不是bug,是典型的线程安全问题。

线程安全,说白了就是“别抢、别乱、别覆盖”

多个线程同时操作同一个变量或对象时,如果没加约束,就像几个人围着一台投币洗衣机——你刚塞钱,他按了启动,她又把硬币掏出来,最后谁也没洗成。线程安全,就是给共享资源上把“锁”,让每次只允许一个人操作。

最常用的几种实现方式

1. 加锁(synchronized)——最直接的“排队机制”

Java里,给方法或代码块加个 synchronized 关键字,就像在门口挂个“请勿打扰”牌:

public synchronized void withdraw(int amount) {
if (balance >= amount) {
balance -= amount;
}
}

同一时间只有一个线程能进这个方法,余额就不会算错。

2. ReentrantLock —— 更灵活的“智能门禁”

比 synchronized 多点控制权,比如可以尝试加锁、设置超时、响应中断:

private final Lock lock = new ReentrantLock();

public void transfer(int amount) {
lock.lock();
try {
if (balance >= amount) {
balance -= amount;
}
} finally {
lock.unlock(); // 一定得释放,否则就死锁了
}
}

3. 原子类(AtomicInteger 等)—— 不用锁的“单步操作”

像 AtomicInteger 的 incrementAndGet() 方法,底层用的是 CPU 的 CAS 指令,一次完成“读-改-写”,天然线程安全:

private AtomicInteger count = new AtomicInteger(0);

public void click() {
count.incrementAndGet(); // 安全+高效,适合计数场景
}

4. 不共享,就不用管 —— “各干各的”哲学

比如用 ThreadLocal 给每个线程配个独立副本,日志追踪ID、用户上下文都这么干:

private static final ThreadLocal<SimpleDateFormat> formatter =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

public String formatNow() {
return formatter.get().format(new Date());
}

每个线程用自己那份 SimpleDateFormat,互不干扰。

小提醒:不是所有地方都要线程安全

局部变量、不可变对象(String、LocalDateTime)、只读集合,本身就不需要额外处理。盲目加锁反而拖慢性能,还可能引发死锁。先想清楚:这数据真会被多个线程同时改吗?改的频率高不高?值是否关键?

就像家里冰箱,大家都能开,但“最后一瓶可乐”的归属,就得有人喊一声:“我先看到的!”——线程安全,本质是协调,不是设防。