数码知识屋
霓虹主题四 · 更硬核的阅读氛围

Java线程同步机制:抢票、转账、计数器,怎么不乱套?

发布时间:2026-01-23 15:41:43 阅读:142 次

你写了个卖电影票的小程序,两个用户同时点“确认购票”,结果同一张票卖出去两次——这不是bug,是线程没管好。

为啥要同步

Java默认允许多线程并发执行,就像菜市场好几个收银员同时结账。但要是他们共用一个库存本子,没人记账就翻页,A记了“还剩1张”,B也看到“还剩1张”,俩人都卖出去,库存就变-1了。

线程同步,就是给共享资源加把锁,谁拿到锁谁才能读写,其他人乖乖排队。

synchronized 最接地气的用法

最常用的是 synchronized 关键字,可以锁方法,也可以锁代码块:

public class Counter {
private int count = 0;

// 锁整个方法(锁的是当前实例 this)
public synchronized void increment() {
count++;
}

// 或者更灵活:只锁关键代码段
public void decrement() {
synchronized(this) {
count--;
}
}
}

注意:静态方法加 synchronized,锁的是类对象(Counter.class),不是 this —— 这意味着所有该类的实例都会被同一把锁拦住。

ReentrantLock:synchronized 的升级版

它支持尝试获取锁(tryLock)、可中断等待、公平锁设置,用起来稍微啰嗦点,但更可控:

import java.util.concurrent.locks.ReentrantLock;

public class BankAccount {
private double balance;
private final ReentrantLock lock = new ReentrantLock();

public void withdraw(double amount) {
if (lock.tryLock()) { // 尝试拿锁,不阻塞
try {
if (balance >= amount) {
balance -= amount;
}
} finally {
lock.unlock(); // 必须在 finally 里释放!
}
} else {
System.out.println("当前繁忙,请稍后再试");
}
}
}

volatile 不是万能锁

有人一听说“可见性”就上 volatile,但它只保证变量修改对其他线程立即可见,**不保证原子性**。比如 i++,读、改、写三步,volatile 拦不住中间插队。

适合场景:状态标志位,如 running = false 停止线程,这种单次写入、无需复合操作的场合。

别忘了还有 Atomic 类

如果只是做自增、比较并交换这类简单操作,AtomicInteger、AtomicReference 更轻量,底层靠 CPU 的 CAS 指令,比加锁快:

import java.util.concurrent.atomic.AtomicInteger;

public class TicketSeller {
private AtomicInteger remaining = new AtomicInteger(100);

public boolean sell() {
int current;
do {
current = remaining.get();
if (current <= 0) return false;
} while (!remaining.compareAndSet(current, current - 1));
return true;
}
}

一句话:synchronized 简单够用,ReentrantLock 灵活可控,Atomic 适合高频单操作,volatile 别乱当锁用——选哪个,得看你手里的那块“共享内存”到底有多娇气。