Java ReentrantLock:从“舔狗式等待”到源码级征服指南

Java ReentrantLock:从"舔狗式等待"到源码级征服指南


一、ReentrantLock 是什么?

1. 可重入锁的"厕所钥匙"哲学

想象你去网吧上厕所,老板给你一把钥匙(锁),你可以反复进出(可重入),但其他人必须排队。

  • 可重入性 :同一线程多次获取锁不会死锁(synchronized也是可重入的)。
  • 灵活控制:支持公平锁(先来后到)、超时锁(舔狗式等待)、可中断锁(及时止损)。

为什么需要它?

synchronized无法满足以下需求时:

  • 需要尝试获取锁(tryLock
  • 需要公平性(防止线程饿死)
  • 需要绑定多个条件(Condition)

二、用法大全:从"Hello Lock"到高端操作

1. 基础姿势:加锁与解锁

java 复制代码
ReentrantLock lock = new ReentrantLock();  
lock.lock();  // 获取锁(阻塞直到成功)  
try {  
    // 临界区代码  
} finally {  
    lock.unlock();  // 必须放在finally,否则可能变"死锁侠"  
}  

注意 :忘记unlock()比忘记女朋友生日更可怕!

2. 高端操作:Condition的"备胎转正"

java 复制代码
Condition notFull = lock.newCondition();  
Condition notEmpty = lock.newCondition();  

// 生产者  
public void put(Object item) throws InterruptedException {  
    lock.lock();  
    try {  
        while (queue.size() == capacity) {  
            notFull.await();  // 队列满时,进入备胎等待区  
        }  
        queue.add(item);  
        notEmpty.signal();  // 唤醒消费者:"有货了!"  
    } finally {  
        lock.unlock();  
    }  
}  

一个锁可以创建多个Condition ,实现精准唤醒(类似Object的wait/notify升级版)。


三、原理揭秘:AQS的"舔狗式等待"算法

1. AQS(AbstractQueuedSynchronizer)的核心思想

  • 状态(state):表示锁的持有次数(可重入的关键)。
  • CLH队列:线程排队等待锁的队列(双向链表结构)。

获取锁流程(非公平模式为例):

  1. 尝试直接抢锁(CAS修改state)。
  2. 抢不到则加入队列尾部,进入"自旋检查+阻塞"的舔狗模式。
  3. 被前驱节点唤醒后再次尝试抢锁。

2. 公平锁 vs 非公平锁

  • 非公平锁(默认):新线程可以插队抢锁,吞吐量高,但可能饿死老线程。
  • 公平锁:严格按队列顺序分配锁,像地铁排队安检。

源码级对比

java 复制代码
// 非公平锁抢锁逻辑  
final void lock() {  
    if (compareAndSetState(0, 1))  // 直接尝试插队  
        setExclusiveOwnerThread(Thread.currentThread());  
    else  
        acquire(1);  
}  

// 公平锁抢锁逻辑  
final void lock() {  
    acquire(1);  // 老老实实排队  
}  

四、避坑指南:从"死锁地狱"到"锁之大道"

1. 经典死锁场景

java 复制代码
// 线程A  
lock1.lock();  
lock2.lock();  

// 线程B  
lock2.lock();  
lock1.lock();  

解决方案

  • 顺序加锁:统一先锁A再锁B。
  • tryLock超时:舔狗不能无限等待!
java 复制代码
if (lock1.tryLock(1, TimeUnit.SECONDS)) {  
    try {  
        if (lock2.tryLock(1, TimeUnit.SECONDS)) {  
            // 成功获取双锁  
        }  
    } finally {  
        lock2.unlock();  
    }  
}  

2. 锁泄漏

java 复制代码
lock.lock();  
// 中间抛出异常,未执行unlock() → 锁永远无法释放!  

正确姿势 :必须把unlock()放在finally块中!


五、最佳实践:美团大佬的锁优化秘籍

1. 锁粒度控制

  • 细粒度锁:只锁必要代码块(如HashMap的每个桶单独加锁)。
  • 锁分段:ConcurrentHashMap用16个Segment减少竞争。

2. 锁命名与监控

java 复制代码
ReentrantLock lock = new ReentrantLock(true);  
lock.setName("OrderLock");  // 自定义锁名,日志排查更友好  

// 监控锁竞争情况  
if (lock.isHeldByCurrentThread()) {  
    log.info("锁被线程{}持有", Thread.currentThread().getName());  
}  

六、面试考点:征服面试官的灵魂拷问

1. ReentrantLock vs synchronized

对比项 ReentrantLock synchronized
实现方式 API层面(AQS) JVM层面(monitor)
锁释放 必须手动unlock() 自动释放
可中断 支持(lockInterruptibly) 不支持
公平性 可配置 非公平
性能 Java 5前优势明显,现在接近 优化后差距缩小

2. AQS的核心实现

  • 模板方法模式 :子类实现tryAcquire/tryRelease
  • CLH队列变体:通过CAS维护等待队列。
  • 自旋优化:减少线程切换开销。

高频追问

  • 为什么AQS用双向链表?
    方便取消等待节点(如超时或中断时快速移除)。
  • state字段如何实现可重入?
    每次重入state+1,释放时state-1,归零时完全释放。

七、总结:锁如宝剑,用好了是神兵,用不好是自刎利器

  • 选型原则 :优先用synchronized,需要高级功能时再选ReentrantLock
  • 核心口诀
    1. 加锁解锁成对出现(像情侣戒指)。
    2. 锁粒度要细(像切蛋糕)。
    3. 死锁预防三招:顺序加锁、超时机制、死锁检测。

最后忠告

"锁越多,坑越深。

无锁胜有锁,

少锁胜多锁,

万不得已再上锁!"


彩蛋

尝试用ReentrantLock实现一个"舔狗追求系统":

  • 女神线程每次回复需等待10秒(Condition.await(10, SECONDS)
  • 舔狗线程用tryLock设置3秒超时,失败后记录"追求失败次数"
  • 当失败次数超过3次,触发lockInterruptibly()及时止损!
相关推荐
程序员JerrySUN2 分钟前
全面理解 Linux 内核性能问题:分类、实战与调优策略
java·linux·运维·服务器·单片机
糯米导航6 分钟前
Java毕业设计:办公自动化系统的设计与实现
java·开发语言·课程设计
糯米导航9 分钟前
Java毕业设计:WML信息查询与后端信息发布系统开发
java·开发语言·课程设计
米粉030527 分钟前
深入剖析Nginx:从入门到高并发架构实战
java·运维·nginx·架构
简诚30 分钟前
HttpURLConnection实现
java
androidwork1 小时前
Android LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout大混战
android·java·kotlin·androidx
陈小桔1 小时前
限流算法java实现
java
黑客老李2 小时前
JavaSec | SpringAOP 链学习分析
java·运维·服务器·开发语言·学习·apache·memcached
勤奋的知更鸟2 小时前
Java编程之原型模式
java·开发语言·原型模式
叶 落2 小时前
[Java 基础]数组
java·java 基础