目录
一、前言简介
二、基础锁——synchronized关键字
三、高级锁——java.util.concurrent.locks包
三、锁的优化技术与实践建议
四、锁的设计原理与内存模型
五、锁的使用对比与选型建议
六、总结归纳
一、前言简介
Java中的锁机制是实现线程同步、保证数据一致性和避免竞态条件的核心工具,主要分为基础锁(内置锁)和高级锁(java.util.concurrent.locks包)。
二、基础锁——synchronized关键字
1. 实现原理
-
基于JVM内置的监视器锁(Monitor),通过对象头的Mark Word实现。
-
通过字节码指令monitorenter和monitorexit控制锁的获取与释放。
2. 使用方式
-
同步代码块:显式指定锁对
public void method() {
synchronized (lockObject) { // lockObject可以是任意对象
// 临界区代码
}
}
-
同步实例方法:锁是当前实例(this)
public synchronized void method() {
// 临界区代码
}
-
同步静态方法:锁是当前类的Class对象
public static synchronized void staticMethod() {
// 临界区代码
}
3. 核心特性
-
可重入性:同一线程可重复获取同一把锁。
-
非公平锁:默认不保证等待线程的获取顺序。
-
自动释放:线程执行完毕或异常时自动释放锁。
-
阻塞等待:未获取锁的线程会进入阻塞状态(BLOCKED)。
4. 优缺点
-
优点:简单易用,无需手动释放锁。
-
缺点:功能单一(不支持超时、中断等)。
三、高级锁——java.util.concurrent.locks包
ReentrantLock(可重入锁)
1.核心特性:
-
可重入性:同synchronized。
-
公平性选择:支持公平锁(new ReentrantLock(true))和非公平锁(默认)。
-
尝试获取锁:tryLock()(立即返回)和tryLock(long timeout, TimeUnit unit)(超时等待)。
-
可中断:lockInterruptibly()响应中断。
-
条件变量:通过newCondition()创建Condition对象,实现精细等待/通知。
2.示例代码:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock(); // 手动加锁
try {
while (conditionNotMet) {
condition.await(); // 释放锁并等待
}
// 临界区代码
condition.signal(); // 唤醒等待线程
} finally {
lock.unlock(); // 必须手动释放锁
}
ReentrantReadWriteLock(读写锁)
1.核心思想:读写分离,提升读多写少场景的性能。
读锁(共享锁):允许多个线程同时读。
写锁(独占锁):只允许一个线程写(且排斥所有读/写)。
2.示例代码:
ReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读操作
rwLock.readLock().lock();
try {
// 并发读操作
} finally {
rwLock.readLock().unlock();
}
// 写操作
rwLock.writeLock().lock();
try {
// 独占写操作
} finally {
rwLock.writeLock().unlock();
}
锁降级:允许从写锁降级为读锁(防止其他写锁插入)。
StampedLock(邮戳锁,Java 8+)
1.三种模式:
-
写锁(Writing Lock):独占锁,类似ReentrantLock。
-
悲观读锁(Pessimistic Reading):类似读写锁的读锁。
-
乐观读(Optimistic Reading):无锁机制,读时不阻塞写操作。
2.优势:性能更高(尤其读多写少场景),但API复杂。
3.示例代码:
StampedLock lock = new StampedLock();
// 乐观读
long stamp = lock.tryOptimisticRead();
// 读操作…
if (!lock.validate(stamp)) { // 检查是否有写操作
stamp = lock.readLock(); // 升级为悲观读锁
try {
// 重新读数据
} finally {
lock.unlockRead(stamp);
}
}
// 写锁
long stamp = lock.writeLock();
try {
// 写操作
} finally {
lock.unlockWrite(stamp);
}
三、锁的优化技术与实践建议
-
锁消除:检测无竞争代码,移除同步操作。
-
锁粗化:合并相邻同步块,减少锁开销。
-
自适应自旋:根据竞争动态调整自旋次数,避免线程阻塞。
-
避免死锁:按固定顺序获取锁,或使用tryLock()超时机制。
-
减小锁粒度:同步范围最小化(如用ConcurrentHashMap分段锁)。
-
短任务 → synchronized(自动优化)。
-
高竞争写入 → 悲观锁。
-
低冲突计数 → CAS原子类。
四、锁的设计原理与内存模型
-
Happens-Before规则:锁释放操作对后续加锁操作可见,确保内存一致性。
-
AQS框架:核心是volatile int state和FIFO队列,通过CAS管理竞争线程(如ReentrantLock实现)。
五、锁的使用对比与选型建议
可重入 | ✅ | ✅ | ✅ | ✅(但需注意模式) |
公平锁 | ❌(仅非公平) | ✅(可配置) | ✅(可配置) | ❌ |
尝试获取锁 | ❌ | ✅(tryLock()) | ✅ | ✅ |
超时等待 | ❌ | ✅ | ✅ | ✅ |
可中断 | ❌ | ✅ | ✅ | ✅ |
条件变量 | wait()/notify() | ✅(Condition) | ✅ | ❌ |
读写分离 | ❌ | ❌ | ✅ | ✅(更灵活) |
乐观读 | ❌ | ❌ | ❌ | ✅ |
锁降级 | ❌ | ❌ | ✅ | ✅ |
-
优先使用 synchronized:简单场景下,JVM对synchronized有持续优化(如锁升级:无锁→偏向锁→轻量级锁→重量级锁)。
-
需要高级功能时选 ReentrantLock:如超时、中断、公平锁、条件变量等。
-
读多写少场景用读写锁:ReentrantReadWriteLock或StampedLock(后者性能更优,但API复杂)。
-
避免锁嵌套:防止死锁(使用tryLock超时机制)。
-
锁粒度最小化:减少竞争(如用ConcurrentHashMap代替synchronizedMap)。
-
注意:StampedLock不是可重入锁,且没有条件变量,需谨慎使用乐观读模式。
六、总结归纳
Java中基础锁(synchronized)简单易用但功能有限,高级锁(如ReentrantLock、读写锁、StampedLock)提供更灵活的并发控制(如公平性、超时、读写分离),需根据场景选择。
评论前必须登录!
注册