外观
Java 有哪些锁
⭐ 题目日期:
京东 - 2024/12/26
📝 题解:
在Java中,锁是并发编程的核心工具,用于控制多线程对共享资源的访问。以下是Java中常见的锁机制及其分类:
一、按锁的实现方式分类
1. 内置锁(隐式锁)
- 关键字:
synchronized
- 用法:修饰方法或代码块。
- 特性:
- 可重入:同一线程可重复获取同一锁。
- 非公平锁:默认抢占式获取锁,不保证线程等待顺序。
- 自动释放:代码块执行完毕或抛出异常时自动释放锁。
- 示例:
public synchronized void method() { ... } // 同步方法 public void block() { synchronized (this) { ... } // 同步代码块 }
2. 显式锁(java.util.concurrent.locks
包)
- 核心类:
ReentrantLock
- 特性:
- 可重入:同内置锁。
- 支持公平性:可指定为公平锁(按等待顺序获取)或非公平锁。
- 灵活控制:支持尝试获取锁(
tryLock()
)、超时等待(tryLock(timeout)
)、可中断(lockInterruptibly()
)。
- 示例:
ReentrantLock lock = new ReentrantLock(true); // 公平锁 lock.lock(); try { // 临界区 } finally { lock.unlock(); }
- 特性:
二、按锁的用途分类
1. 读写锁(ReentrantReadWriteLock
)
- 核心思想:读共享,写独占。
- 读锁(共享锁):允许多个线程同时读。
- 写锁(排他锁):同一时间仅一个线程可写。
- 适用场景:读多写少的高并发场景(如缓存)。
- 示例:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); rwLock.readLock().lock(); // 获取读锁 rwLock.writeLock().lock(); // 获取写锁
2. 乐观锁(无锁编程)
- 实现方式:CAS(Compare and Swap)操作,如
AtomicInteger
、AtomicReference
。- 原理:通过版本号或值比较实现原子更新。
- 优点:避免线程阻塞,适用于低竞争场景。
- 示例:
AtomicInteger counter = new AtomicInteger(0); counter.incrementAndGet(); // 原子递增
3. 分布式锁
- 场景:跨进程或跨机器的资源同步。
- 实现方式:依赖外部系统(如Redis、ZooKeeper)。
- Redis实现:使用
SETNX
命令或 RedLock 算法。 - ZooKeeper实现:通过临时顺序节点实现。
- Redis实现:使用
三、按锁的特性分类
1. 可重入锁
- 定义:线程可重复获取已持有的锁(避免死锁)。
- 实现:
synchronized
、ReentrantLock
、ReentrantReadWriteLock
。
2. 公平锁 vs 非公平锁
- 公平锁:按线程等待顺序分配锁(
ReentrantLock(true)
)。 - 非公平锁:允许线程插队(默认模式,吞吐量更高)。
3. 悲观锁 vs 乐观锁
- 悲观锁:假设会发生竞争,直接加锁(如
synchronized
)。 - 乐观锁:假设无竞争,通过版本控制实现(如 CAS)。
四、高级锁工具
1. StampedLock
(Java 8+)
- 特点:支持乐观读、读写锁升级/降级。
- 乐观读:不阻塞写操作,读后需验证数据一致性。
- 适用场景:读远多于写,且读操作较耗时。
- 示例:
StampedLock lock = new StampedLock(); long stamp = lock.tryOptimisticRead(); // 乐观读 if (!lock.validate(stamp)) { // 验证是否被修改 stamp = lock.readLock(); // 退化为悲观读 }
2. LockSupport
- 底层工具:直接操作线程的阻塞与唤醒。
- 核心方法:
park()
:阻塞当前线程。unpark(Thread)
:唤醒指定线程。
- 用途:实现自定义同步器(如AQS)。
- 核心方法:
3. Condition
- 与显式锁配合:替代
Object.wait()
/notify()
,提供更灵活的等待/通知机制。- 示例:
ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); lock.lock(); try { condition.await(); // 等待 condition.signal(); // 唤醒 } finally { lock.unlock(); }
- 示例:
五、锁的性能与选择建议
场景 | 推荐锁类型 | 理由 |
---|---|---|
简单同步 | synchronized | 代码简洁,JVM自动优化(如锁升级)。 |
高竞争、灵活控制 | ReentrantLock | 支持超时、中断、公平性。 |
读多写少 | ReentrantReadWriteLock | 提升读并发性能。 |
读远多于写,长读操作 | StampedLock (乐观读) | 减少写线程阻塞。 |
无锁编程(低竞争) | CAS(如 AtomicInteger ) | 避免线程上下文切换。 |
六、锁的注意事项
- 死锁预防:避免嵌套锁、使用超时机制。
- 锁粒度:尽量减小锁的作用范围(如锁代码块而非方法)。
- 性能监控:关注锁竞争(如JVM参数
-XX:+PrintLockStatistics
)。
通过合理选择锁机制,可以显著提升多线程程序的性能和稳定性。理解每种锁的特性和适用场景是关键!