Java 锁机制详解:用“厕所门”和“防盗门”轻松理解多线程同步
Java 锁机制详解:用“厕所门”和“防盗门”轻松理解多线程同步
目录
- 锁的作用
- synchronized 关键字
- ReentrantLock
- ReadWriteLock
- StampedLock
- 避免死锁的诀窍
- 总结与对比
锁的作用
生活中的例子:公共厕所一次只能进一人,门上的“有人/无人”标志就是锁。
程序中的作用:当多个线程操作共享资源(如银行账户余额)时,锁保证数据安全。
synchronized 关键字
1. 同步方法
class BankAccount {
private int balance = 100;
// 锁住整个对象
public synchronized void withdraw(int amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " 取款 " + amount);
balance -= amount;
}
}
}
类比:厕所门自动上锁,其他人必须等待。
2. 同步代码块
public void withdraw(int amount) {
// 只锁关键代码
synchronized(this) {
if (balance >= amount) {
balance -= amount;
}
}
}
优势:缩小锁范围,提高效率。
ReentrantLock
import java.util.concurrent.locks.ReentrantLock;
class TicketSeller {
private int tickets = 10;
private ReentrantLock lock = new ReentrantLock();
public void sellTicket() {
lock.lock(); // 手动加锁
try {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出第 " + tickets--);
}
} finally {
lock.unlock(); // 必须手动释放!
}
}
}
特点:
- 支持公平锁(
new ReentrantLock(true)
) - 可尝试获取锁(
tryLock()
) - 可中断等待(
lockInterruptibly()
)
类比:手动开关的防盗门,灵活控制进出规则。
ReadWriteLock
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class Cache {
private Object data = null;
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读操作:共享锁
public Object getData() {
rwLock.readLock().lock();
try {
return data;
} finally {
rwLock.readLock().unlock();
}
}
// 写操作:独占锁
public void updateData(Object newData) {
rwLock.writeLock().lock();
try {
data = newData;
} finally {
rwLock.writeLock().unlock();
}
}
}
适用场景:读多写少(如商品库存查询)。
StampedLock
import java.util.concurrent.locks.StampedLock;
class Point {
private double x, y;
private StampedLock lock = new StampedLock();
// 乐观读:假设写操作很少发生
public double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead();
double currentX = x, currentY = y;
if (!lock.validate(stamp)) { // 检查是否有写操作
stamp = lock.readLock(); // 转为悲观读锁
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
特点:在读多写少时性能更高,但实现复杂。
避免死锁的诀窍
经典死锁场景:两人在独木桥相遇,互不相让。
解决方案:
- 固定顺序获取锁:比如先锁 A 再锁 B。
- 设置超时时间:
tryLock(5, TimeUnit.SECONDS)
。
总结与对比
锁类型 | 特点 | 适用场景 |
---|---|---|
synchronized | 自动加锁/释放,简单但性能较低 | 简单同步需求 |
ReentrantLock | 手动控制,支持公平锁/条件变量 | 复杂同步场景 |
ReadWriteLock | 读写分离,提升读性能 | 读多写少(如缓存) |
StampedLock | 乐观读,性能最高但实现复杂 | 极高并发读,极少写 |
提示:实际开发中优先选择
synchronized
,需要高级功能时再考虑其他锁。