一文解析公平锁、非公平锁、悲观锁、乐观锁、可重入锁和锁的升级(含详细代码实例)
在并发开发和数据库事务处理中,锁机制是绕不开的难题。了解锁的种类、原理和应用场景,对于写出高效安全的并发程序非常重要。本文将系统总结公平锁、非公平锁、悲观锁、乐观锁、可重入锁、锁的升级等核心知识,并配套详细的实用代码,带你搞懂常见锁机制!
一、公平锁与非公平锁
1. 公平锁(Fair Lock)
定义:获取锁的顺序按照线程到来的顺序进行分配,先请求锁的线程先获得锁,避免线程“饥饿”。
应用场景:对执行先后顺序非常敏感的场合,如任务调度、并发队列等。
代码示例(Java):
import java.util.concurrent.locks.ReentrantLock;public class FairLockDemo {// true 表示公平锁private static final ReentrantLock fairLock = new ReentrantLock(true);public void testLock() {fairLock.lock();try {System.out.println(Thread.currentThread().getName() + " 获得了公平锁");Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();} finally {fairLock.unlock();}}public static void main(String[] args) {FairLockDemo demo = new FairLockDemo();for (int i = 0; i < 5; i++) {new Thread(demo::testLock, "Thread-" + i).start();}}
}
2. 非公平锁(Nonfair Lock)
定义:线程加锁的顺序不严格按照申请顺序,允许“插队”,提高吞吐量。
应用场景:大多数并发场景,追求性能。
代码示例(Java):
import java.util.concurrent.locks.ReentrantLock;public class NonFairLockDemo {// 默认 false,非公平锁private static final ReentrantLock lock = new ReentrantLock();public void testLock() {lock.lock();try {System.out.println(Thread.currentThread().getName() + " 获得了非公平锁");Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public static void main(String[] args) {NonFairLockDemo demo = new NonFairLockDemo();for (int i = 0; i < 5; i++) {new Thread(demo::testLock, "Thread-" + i).start();}}
}
拓展: synchronized
在 JVM 层面也是一种非公平锁。
二、悲观锁与乐观锁
1. 悲观锁(Pessimistic Lock)
定义:假设系统“很可能”发生并发冲突,每次数据操作都先加锁,其他线程只能等待。
应用场景:并发写多于读,且数据一致性要求高,例如订单、资金等核心业务。
数据库实现:
-- 事务中加锁(MySQL InnoDB)
START TRANSACTION;
SELECT * FROM account WHERE id=1 FOR UPDATE;
-- do something...
UPDATE account SET balance=balance-100 WHERE id=1;
COMMIT;
Java实现(synchronized / Lock):
public class PessimisticLockDemo {public synchronized void doSomething() {System.out.println(Thread.currentThread().getName() + " 获得了悲观锁");// 执行“临界区”操作}
}
2. 乐观锁(Optimistic Lock)
定义:假定冲突较少,不上锁,通过“版本号”或CAS检查数据是否被别人修改。若被修改则重试。
应用场景:用户数大、并发冲突不多的场景,如商品秒杀、用户信息查询等。
数据库实现(版本号字段):
-- 查询时一并获取 version,例如5
UPDATE account SET balance=balance-100, version=version+1 WHERE id=1 AND version=5;
-- 如果该SQL更新数为0,说明数据已被别的线程改过,需要重试
Java实现(CAS 原子类):
import java.util.concurrent.atomic.AtomicInteger;public class OptimisticLockDemo {private AtomicInteger value = new AtomicInteger(0);public void tryUpdate() {int oldVal, newVal;do {oldVal = value.get();newVal = oldVal + 1;} while (!value.compareAndSet(oldVal, newVal));System.out.println("成功更新为:" + newVal);}
}
三、可重入锁(Reentrant Lock)
定义:同一线程多次申请同一把锁不会被阻塞,内部会维护锁“重入次数”。
作用:方便递归、父子方法重复加锁的场景,避免死锁。
1. synchronized的可重入性:
public class ReentrantSyncDemo {public synchronized void outer() {System.out.println("outer....");inner();}public synchronized void inner() {System.out.println("inner....");}public static void main(String[] args) {new ReentrantSyncDemo().outer();}
}
2. ReentrantLock的可重入性:
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockDemo {private final ReentrantLock lock = new ReentrantLock();public void methodA() {lock.lock();try {System.out.println("进入methodA");methodB();} finally {lock.unlock();}}public void methodB() {lock.lock();try {System.out.println("进入methodB");} finally {lock.unlock();}}
}
四、锁的升级(锁的优化机制)
定义:JVM对synchronized
的性能优化,锁的状态会随竞争程度自动“升级”,包括偏向锁、轻量级锁和重量级锁。
锁的状态与升级流程:
- 无锁:对象未被线程持有。
- 偏向锁:假设只有一条线程反复获得锁,极高性能(无任何同步原语)。
- 轻量级锁:短期竞争时的自旋锁(无需阻塞,性能高)。
- 重量级锁:竞争激烈时的传统监视器锁(阻塞/唤醒)。
状态升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
(注意:不会降级)
代码观察(JDK源码、JOL工具辅助理解):
public class LockUpgradeDemo {public static void main(String[] args) throws Exception {Object obj = new Object();synchronized (obj) {// 查看对象头信息System.out.println("进入同步块");}}
}
使用JOL(Java Object Layout)库可以详细观察对象头信息及锁状态变化。
五、总结对比表
类型 | 定义 | 场景 | 代码说明 | 优缺点 |
---|---|---|---|---|
公平锁 | 先到先得,排队获取锁 | 银行排队 | new ReentrantLock(true) | 避免饿死但慢 |
非公平锁 | 不按顺序,允许插队 | 绝大多数并发场景 | new ReentrantLock(false) 或synchronized | 性能高风险饿死 |
悲观锁 | 先锁后用,强制同步 | 核心交易、资金 | synchronized /数据库for update | 安全性能一般 |
乐观锁 | 无锁冲突检测后重试 | 高频读、低冲突 | Java原子类、版本号字段 | 快,不适用多冲突 |
可重入锁 | 自身可多次获得同一锁 | 递归与多调用场景 | synchronized 、ReentrantLock | 灵活无死锁 |
锁的升级 | JVM智能优化锁粒度 | JVM自动控制 | synchronized (自动实现) | 性能最优动态选择 |
六、开发实践
- 公平锁用于严格保证顺序,通常业务场景非必须;
- 乐观锁适用于冲突概率小、性能要求高的场合;
- 悲观锁安全但性能低,不适于高并发的热点资源;
- 可重入锁极大提高编码灵活性,避免递归死锁;
- JVM锁优化机制让大部分场合无需人工干涉锁粒度;
- 项目开发建议优先选择简单的
synchronized
,高并发场景评估使用ReentrantLock
及乐观锁。