Java中有哪些锁?
1. synchronized
锁
作用:
synchronized
是 Java 最基础的锁机制,用于实现方法或代码块的同步,保证多线程环境下的原子性、可见性和有序性。
使用方式:
-
对象锁:修饰实例方法或代码块(锁对象为当前实例
this
)。public synchronized void method() { ... } // 实例方法锁 public void method() {synchronized (this) { ... } // 代码块锁(对象锁) }
-
类锁:修饰静态方法或代码块(锁对象为类的
Class
对象)。public static synchronized void staticMethod() { ... } // 静态方法锁 public void method() {synchronized (MyClass.class) { ... } // 代码块锁(类锁) }
特点:
-
隐式锁:自动获取和释放锁,无需手动管理。
-
可重入:线程可重复获取同一把锁。
-
非公平锁:默认抢占式获取锁,不保证等待时间长的线程优先获取。
适用场景:
简单同步需求,如单例模式的双重检查锁。
2. ReentrantLock
(可重入锁)
作用:
ReentrantLock
是 java.util.concurrent.locks
包下的显式锁,提供比 synchronized
更灵活的锁控制。
使用方式:
ReentrantLock lock = new ReentrantLock();
public void method() {lock.lock(); // 手动加锁try {// 临界区代码} finally {lock.unlock(); // 必须手动释放}
}
核心功能:
-
尝试非阻塞获取锁:
tryLock()
。 -
超时获取锁:
tryLock(long timeout, TimeUnit unit)
。 -
公平性选择:构造函数传入
true
实现公平锁(按等待顺序分配锁)。 -
条件变量:配合
Condition
实现线程间协调(如生产者-消费者模型)。
优缺点:
-
优点:灵活,支持中断、超时、公平锁。
-
缺点:需手动释放锁,编码不当易导致死锁。
适用场景:
需要复杂同步控制的场景,如多条件等待、锁超时处理。
3. ReadWriteLock
(读写锁)
作用:
允许多个线程同时读,但写线程独占资源,适用于读多写少的场景。
实现类:ReentrantReadWriteLock
。
使用方式:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();public void readData() {readLock.lock();try { ... } finally { readLock.unlock(); }
}public void writeData() {writeLock.lock();try { ... } finally { writeLock.unlock(); }
}
特点:
-
读共享:多个读线程可同时访问。
-
写独占:写线程获取锁时,禁止其他读/写操作。
-
锁降级:写锁可降级为读锁(反之不可)。
适用场景:
缓存系统、频繁读取但偶尔更新的数据结构。
4. StampedLock
(Java 8+)
作用:
提供更灵活的读写锁控制,支持乐观读、悲观读、写锁三种模式,性能优于 ReadWriteLock
。
使用方式:
StampedLock stampedLock = new StampedLock();// 乐观读(无锁)
long stamp = stampedLock.tryOptimisticRead();
if (!stampedLock.validate(stamp)) { // 检查是否发生写操作stamp = stampedLock.readLock(); // 退化为悲观读try { ... } finally { stampedLock.unlockRead(stamp); }
}// 写锁
long writeStamp = stampedLock.writeLock();
try { ... } finally { stampedLock.unlockWrite(writeStamp); }
特点:
-
乐观读:假设没有写操作,通过
validate()
验证。 -
锁转换:支持读锁与写锁的转换(如
tryConvertToWriteLock()
)。
适用场景:
读多写少且对性能要求极高的场景,但需谨慎处理锁转换逻辑。
5. 显式锁 vs 隐式锁
维度 | 显式锁(如 ReentrantLock ) | 隐式锁(synchronized ) |
---|---|---|
锁获取方式 | 手动调用 lock() 和 unlock() | 自动获取和释放 |
灵活性 | 支持超时、中断、公平锁、条件变量 | 仅支持非公平锁,无超时或中断机制 |
性能 | 高并发场景下更优 | 优化后性能接近显式锁(如锁升级机制) |
代码复杂度 | 需手动管理,易出错 | 自动管理,代码简洁 |
6. 锁优化策略(JVM 对 synchronized
的优化)
-
偏向锁:
假设只有一个线程访问同步块,通过标记线程 ID 避免 CAS 操作。
适用场景:单线程重复访问同步代码。 -
轻量级锁:
当多线程竞争时,通过 CAS 自旋尝试获取锁(避免阻塞)。
适用场景:低竞争、同步块执行时间短。 -
重量级锁:
竞争激烈时,线程进入阻塞状态,依赖操作系统的互斥量(Mutex)管理。
适用场景:高竞争、同步块执行时间长。
锁升级流程:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
7. 条件锁(Condition
)
作用:
配合显式锁(如 ReentrantLock
)使用,实现线程的等待/唤醒机制,类似 Object.wait()
和 Object.notify()
,但更灵活。
使用方式:
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();public void await() throws InterruptedException {lock.lock();try {condition.await(); // 释放锁并等待} finally { lock.unlock(); }
}public void signal() {lock.lock();try {condition.signal(); // 唤醒一个等待线程} finally { lock.unlock(); }
}
适用场景:
生产者-消费者模型、多条件线程协调。
8. 分布式锁
作用:
在分布式系统中协调多节点对共享资源的访问,防止并发冲突。
实现方式:
-
Redis:通过
SETNX
(或 RedLock 算法)实现。// 使用 Redisson 客户端 RLock lock = redisson.getLock("myLock"); lock.lock(); try { ... } finally { lock.unlock(); }
-
ZooKeeper:通过临时有序节点实现。
-
数据库:利用唯一索引或乐观锁(版本号)。
核心挑战:
-
死锁预防:设置锁超时时间。
-
容错性:避免单点故障(如 Redis 集群)。
总结
锁类型 | 核心特点 | 适用场景 |
---|---|---|
synchronized | 简单、自动管理,支持锁升级优化 | 简单同步需求 |
ReentrantLock | 灵活,支持超时、公平锁、条件变量 | 复杂同步控制(如多条件等待) |
ReadWriteLock | 读写分离,读多写少场景性能更优 | 缓存、频繁读的数据结构 |
StampedLock | 高性能,支持乐观读 | 极高并发读场景 |
分布式锁 | 跨进程、跨节点协调 | 分布式系统资源共享 |
选择建议:
-
优先使用
synchronized
(简单场景)。 -
需要灵活控制时选
ReentrantLock
。 -
读多写少用
ReadWriteLock
或StampedLock
。 -
分布式环境用 Redis/ZooKeeper 实现分布式锁。