Java中存在哪些锁?
一、内置锁(Synchronized)
Java 语言原生支持的锁机制,基于 JVM 实现,无需显式释放。
- 特性:
- 自动获取 / 释放锁(代码块执行完或异常时自动释放)。
- 可修饰方法(锁住当前对象或类)或代码块(锁住指定对象)。
- JDK 6 后优化为 “偏向锁 → 轻量级锁 → 重量级锁” 的升级过程(自适应锁)。
- 示例:
// 修饰方法(锁住当前对象) synchronized void method() { ... }// 修饰代码块(锁住指定对象) Object lock = new Object(); void method() {synchronized (lock) { ... } }
二、显式锁(java.util.concurrent.locks)
JDK 5+ 引入的锁框架,需手动获取和释放,功能更灵活。
1. ReentrantLock(可重入锁)
- 特性:
- 支持重入(同一线程可多次获取锁,需对应次数释放)。
- 可中断(
lockInterruptibly()
允许线程在等待锁时响应中断)。 - 可设置公平性(构造函数传入
true
为公平锁,按申请顺序获取锁)。 - 支持条件变量(
Condition
,实现更灵活的等待 / 通知机制)。
- 示例:
ReentrantLock lock = new ReentrantLock(true); // 公平锁 try {lock.lock(); // 获取锁// 临界区代码 } finally {lock.unlock(); // 释放锁(必须在finally中) }
2. ReentrantReadWriteLock(读写锁)
- 特性:
- 分离读锁和写锁,允许多个线程同时读(共享),但写操作独占。
- 读锁与写锁互斥,写锁与写锁互斥,适合 “读多写少” 场景。
- 支持重入和公平性设置。
- 示例:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock(); ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();// 读操作(共享) readLock.lock(); try { ... } finally { readLock.unlock(); }// 写操作(独占) writeLock.lock(); try { ... } finally { writeLock.unlock(); }
3. StampedLock( stamped 锁)
JDK 8 引入,优化读写锁性能,支持三种模式:
- 写锁(独占):与
ReentrantReadWriteLock
的写锁类似。 - 悲观读锁(共享):与普通读锁类似,允许多线程读。
- 乐观读:无锁模式,读取时不阻塞写操作,需校验版本号(
stamp
)判断数据是否被修改。
适合 “读极多写极少” 场景,性能优于ReentrantReadWriteLock
。
三、分布式锁
跨 JVM 进程的锁机制,用于分布式系统中多节点的资源竞争(如 Redis、ZooKeeper 实现)。
- 特性:
- 基于第三方组件(如 Redis 的
SET NX
命令、ZooKeeper 的临时节点)实现。 - 需处理锁超时、释放异常、重入性等问题。
- 基于第三方组件(如 Redis 的
- 示例(Redis 分布式锁简化逻辑):
// 获取锁(SET NX + 过期时间) Boolean locked = redisTemplate.opsForValue().setIfAbsent("lockKey", "value", 30, TimeUnit.SECONDS); if (locked) {try {// 业务逻辑} finally {// 释放锁(需校验是否为当前线程持有)redisTemplate.delete("lockKey");} }
四、其他特殊锁
偏向锁 / 轻量级锁 / 重量级锁(Synchronized 的升级过程):
- 偏向锁:单线程场景下,消除锁竞争开销(仅记录线程 ID)。
- 轻量级锁:多线程交替执行时,用 CAS 避免重量级锁的内核态切换。
- 重量级锁:多线程竞争激烈时,依赖操作系统互斥量(Mutex)实现,开销大但安全。
自旋锁:线程获取锁失败时,不立即阻塞,而是循环重试(自旋),减少上下文切换,适合锁持有时间短的场景(JDK 中的
Unsafe
类提供 CAS 实现)。公平锁 / 非公平锁:
- 公平锁:按线程申请锁的顺序获取,避免饥饿,但性能较低(如
ReentrantLock(true)
)。 - 非公平锁:允许线程 “插队” 获取锁,可能导致饥饿,但性能更高(默认模式)。
- 公平锁:按线程申请锁的顺序获取,避免饥饿,但性能较低(如
总结
Java 中的锁按范围可分为本地锁(Synchronized、ReentrantLock 等)和分布式锁;按特性可分为可重入锁、读写锁、公平锁等。
扩展:
1、Synchronized修饰方法(锁住当前对象或类)或代码块(锁住指定对象)二者有什么不同?
锁对象的区别:
修饰普通方法
- 锁的对象是当前实例对象(
this
)。 - 多个个线程调用同一个对象的该方法时,会竞争同一把锁;但调用不同对象的该方法时,互不干扰(每个对象有独立的锁)。
修饰静态方法
- 锁的对象是当前类的 Class 对象(每个类只有一个 Class 对象,全局唯一)。
- 无论多少个实例对象,调用该静态方法时都会竞争同一把锁(类级别的锁)
修饰代码块
- 锁的对象是代码块中显式指定的对象(可以是
this
、类对象、自定义对象等)。 - 灵活性更高,可根据需求选择锁的范围(实例级、类级或自定义对象级)。
其余区别:
维度 | synchronized 修饰方法 | synchronized 修饰代码块 |
---|---|---|
锁对象 | 普通方法:当前实例(this );静态方法:类对象(Xxx.class ) | 显式指定的对象(this 、类对象、自定义对象等) |
锁粒度 | 粗(整个方法) | 细(仅代码块) |
灵活性 | 低(锁对象固定) | 高(可自由选择锁对象和范围) |
性能 | 可能较差(同步范围大) | 通常更优(同步范围可控) |
适用场景 | 整个方法都需要同步的简单场景 | 仅部分代码需要同步的复杂场景 |