作为 Java 并发编程的终极武器,Lock 接口在高手手中能爆发出惊人的性能!本文将深度剖析其核心原理和实战技巧,助你彻底掌握这把高性能锁!
在并发编程中,传统的 synchronized
关键字虽然简单易用,但在高并发场景下暴露出四大痛点:
这直接催生了 JUC 的 Lock 接口(java.util.concurrent.locks
),直击 synchronized 的软肋!
能力维度 | synchronized | Lock | 实战价值 |
---|---|---|---|
锁获取方式 | JVM 自动管理 | 手动 lock()/unlock() |
精细控制锁生命周期 |
中断响应 | ❌ 不可中断 | ✅ lockInterruptibly() |
避免死锁必备能力 |
超时控制 | ❌ 无限等待 | ✅ tryLock(time, unit) |
高并发系统保命技能 |
公平策略 | ❌ 仅非公平 | ✅ 可配置公平锁 | 防止线程饥饿 |
多条件队列 | ❌ 单一等待队列 | ✅ newCondition() |
复杂等待场景轻松实现 |
锁状态监控 | ❌ 无 | ✅ getQueueLength() 等 |
并发调试神器 |
性能表现 | 一般(JDK6+优化后尚可) | ⭐⭐⭐ 极高(尤其在竞争激烈时) | 百万级并发核心支撑 |
重要结论:在超时控制、复杂等待、公平性要求等场景中,Lock 是绝对首选!
作为 Lock 的旗舰实现,ReentrantLock
是面试必考点,掌握它让你在并发战场游刃有余!
// 创建非公平锁(默认:96%场景的最佳选择)
ReentrantLock lock = new ReentrantLock();
// 创建公平锁(特殊场景使用,性能损失20%~30%)
ReentrantLock fairLock = new ReentrantLock(true);
// 1. 阻塞式获取锁(同 synchronized)
lock.lock();
// 2. 可中断锁(避免死锁救星)
lock.lockInterruptibly();
// 3. 非阻塞尝试(立即返回)
if(lock.tryLock()) { /* 抢锁成功 */ }
// 4. 超时控制(高并发必备)
if(lock.tryLock(3, TimeUnit.SECONDS)) { /* 3秒内抢到锁 */ }
Lock lock = new ReentrantLock();
// 正确姿势:lock() 放在 try 外!
lock.lock(); // 防止未获取锁却执行 unlock 的异常
try {
// 临界区代码(受保护资源访问)
} finally {
lock.unlock(); // 100%确保释放锁,避免死锁
}
⚠️ 血泪教训:忘记 unlock 引发的死锁问题最难排查!
非公平锁(默认):线程可插队抢锁
公平锁:严格 FIFO 排队
// 实战建议:
// - 95%场景用非公平锁(性能至上)
// - 线程执行时间差异大时用公平锁(避免饥饿)
public void transfer(Account from, Account to, int amount)
throws InterruptedException {
// 尝试获取第一把锁(可中断)
from.lock.lockInterruptibly();
try {
// 尝试获取第二把锁(可中断)
to.lock.lockInterruptibly();
try {
// 执行转账操作
from.withdraw(amount);
to.deposit(amount);
} finally {
to.lock.unlock();
}
} finally {
from.lock.unlock();
}
}
精妙之处:当发生死锁时,通过
Thread.interrupt()
可中断阻塞线程,完美解决死锁!
if (!lock.tryLock(300, TimeUnit.MILLISECONDS)) {
// 降级策略:快速失败返回
throw new BusyException("系统繁忙,请重试");
// 或者异步记录日志
// auditLog.warn("锁获取超时,资源ID:{}", resourceId);
}
try {
// 访问共享资源
} finally {
lock.unlock();
}
重要场景:数据库连接池、API限流器、秒杀系统等高并发服务必须使用!
当你需要实现复杂的等待条件时,Condition 直接碾压 Object 的 wait/notify!
class BoundedBuffer {
final Lock lock = new ReentrantLock();
// 两个条件:队列非空、队列非满
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 等待"非满"条件
// 生产数据
notEmpty.signal(); // 唤醒消费者
} finally {
lock.unlock();
}
}
Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 等待"非空"条件
// 消费数据
notFull.signal(); // 唤醒生产者
return x;
} finally {
lock.unlock();
}
}
}
方法 | 说明 | 超能力 |
---|---|---|
void await() |
释放锁等待 | ✅ 响应中断 |
void awaitUninterruptibly() |
等待(不响应中断) | ❌ 特殊场景使用 |
long awaitNanos(long) |
纳秒级超时等待 | ⏱️ 超精确控制 |
boolean awaitUntil(Date) |
截止时间等待 | ️ 绝对时间控制 |
void signal() |
唤醒单个线程 | 精确唤醒 |
void signalAll() |
唤醒所有线程 | 广播唤醒 |
重要对比:一个 Lock 可创建多个 Condition,实现精准唤醒,比 notifyAll() 高效100倍!
ReentrantLock 内置了强大的锁状态监控能力,性能调优必备工具:
ReentrantLock lock = new ReentrantLock();
// 获取锁状态(调试神器)
System.out.println("等待线程数: " + lock.getQueueLength());
System.out.println("持有者: " + lock.getOwner());
System.out.println("当前线程持有数: " + lock.getHoldCount());
System.out.println("是否有等待线程: " + lock.hasQueuedThreads());
// 输出示例:
// 等待线程数: 3
// 持有者: Thread[worker-1,5,main]
// 当前线程持有数: 1
// 是否有等待线程: true
️ 实战应用:结合 Spring Boot Actuator 自定义监控端点,实时感知系统锁竞争!
graph TD
A[需要同步控制] --> B{是否简单同步?}
B -->|简单| C[synchronized]
B -->|复杂| D{需要以下特性?}
D --> E[超时/中断控制] --> H[选Lock]
D --> F[多条件等待] --> H
D --> G[公平性要求] --> H
D --> I[锁状态监控] --> H
资源名+Lock
(如 orderPayLock)除了 ReentrantLock,这些锁在特定场景更出色:
锁类型 | 适用场景 | 性能特点 |
---|---|---|
ReentrantReadWriteLock |
读多写少 | 读并行极高 |
StampedLock |
乐观读(Java8+) | 比读写锁更快 |
Semaphore |
资源数量控制 | 经典限流器 |
CountDownLatch |
多线程任务等待 | 火箭发射倒计时模式 |
下期预告:《ReentrantReadWriteLock 深度解密:百万级并发的秘密》
用 synchronized 当:
用 Lock 当:
彩蛋:使用锁的黄金比例 - 在 50 个线程并发场景下:
掌握这些硬核知识,在下一个高并发系统设计中,你就是那个力挽狂澜的架构师!