文章目录
- 深入解析Java中的可重入锁ReentrantLock
- 一、什么是ReentrantLock?
- 二、为什么要用ReentrantLock?
- 三、ReentrantLock的核心特性
- 1. **可重入性**
- 2. **公平性和非公平性**
- 3. **锁的状态**
- 四、ReentrantLock vs synchronized
- 五、ReentrantLock的使用场景
- 1. 高并发场景
- 2. 公平性要求高的场景
- 3. 需要可重入性的场景
- 六、ReentrantLock的注意事项
- 1. **避免死锁**
- 2. **防止资源泄漏**
- 3. **避免使用过多的锁**
- 七、实战案例:实现一个简单的银行转账
- 案例描述:
- 实现代码:
- 分析:
- 八、总结
- 希望这篇长文能帮助你更好地理解和使用ReentrantLock!如果还有疑问或者想深入探讨的地方,欢迎随时交流。
- 📚 领取 | 1000+ 套高质量面试题大合集(无套路,闫工带你飞一把)!
深入解析Java中的可重入锁ReentrantLock
各位朋友们大家好!闫工又来给大家讲技术了!这次我们来聊一聊Java中一个非常重要的同步工具——ReentrantLock(可重入锁)。相信很多小伙伴在学习Java多线程的时候,或多或少都听说过这个词。但是,你真的搞懂它是什么、怎么用以及它的那些“小心机”吗?别急,闫工带着你们一步步深入,手把手教会你!
一、什么是ReentrantLock?
首先,让我们从基础开始。ReentrantLock是Java中一个非常强大的锁机制,属于java.util.concurrent.locks包。它主要用于在多线程环境下控制对共享资源的访问,避免多个线程同时修改或读取同一块数据时出现混乱。
ReentrantLock的名字中有两个关键词:
- Reentrant:意思是“可重入”的,表示同一个线程可以多次获取同一个锁。
- Lock:当然就是锁的意思啦!
简单来说,ReentrantLock就是一个能够被多个线程共享、并且支持公平与非公平模式的高级锁机制。
二、为什么要用ReentrantLock?
在Java中,默认的同步方式是synchronized关键字。虽然synchronized非常简单易用,但它也有一些缺点:
- 粒度控制不够细:无法实现更复杂的锁逻辑。
- 性能问题:在某些场景下,synchronized的表现不如显式的锁机制。
- 缺乏公平性支持:默认情况下,synchronized是不公平的。
而ReentrantLock则弥补了这些不足。它提供了更多的灵活性和控制力,比如:
- 支持可重入(同一个线程可以多次加锁)。
- 可以选择公平或非公平的锁获取方式。
- 提供条件队列(Condition),可以实现更复杂的同步逻辑。
三、ReentrantLock的核心特性
1.可重入性
“可重入”是ReentrantLock的最大特色之一。它允许同一个线程多次获得同一个锁,而不会出现死锁的情况。例如:
publicclassReentrantTest{privatefinalReentrantLocklock=newReentrantLock();publicvoidmethodA(){lock.lock();try{System.out.println("methodA acquire lock");methodB();}finally{lock.unlock();}}publicvoidmethodB(){lock.lock();try{System.out.println("methodB acquire lock");}finally{lock.unlock();}}publicstaticvoidmain(String[]args){ReentrantTesttest=newReentrantTest();test.methodA();}}在这个例子中,methodA()调用了methodB(),而两者都尝试获取同一个锁。如果没有可重入性,第二次lock.lock()会阻塞甚至导致死锁。但ReentrantLock支持这一点,所以程序可以正常运行。
2.公平性和非公平性
ReentrantLock有两个模式:公平锁和非公平锁。默认情况下是非公平锁,即先到的线程不一定能优先获取锁。
- 非公平锁:当一个线程尝试获取锁时,如果锁是可用的,则立即获得;否则,排队等待。
- 公平锁:严格按照线程到达的顺序来分配锁,先进先得。
可以通过构造函数指定模式:
// 非公平锁(默认)ReentrantLocklock=newReentrantLock();// 公平锁ReentrantLockfairLock=newReentrantLock(true);3.锁的状态
ReentrantLock内部维护了一个计数器,用来记录锁的重入次数。每次成功加锁,计数器递增;每次成功解锁,计数器递减。
publicclassLockState{privatefinalReentrantLocklock=newReentrantLock();publicvoidcheck(){System.out.println("Lock hold count: "+lock.getHoldCount());System.out.println("Number of waiting threads: "+lock.getQueueLength());}publicstaticvoidmain(String[]args){LockStatestate=newLockState();state.check();}}getHoldCount()返回当前线程对锁的持有次数,而getQueueLength()返回正在等待获取该锁的线程数。
四、ReentrantLock vs synchronized
很多人可能会问:既然有了synchronized,为什么还要用ReentrantLock?其实两者的区别还是挺大的:
| 特性 | ReentrantLock | synchronized |
|---|---|---|
| 可重入性 | 支持 | 不支持 |
| 公平性选择 | 可选(公平/非公平) | 无 |
| 锁粒度控制 | 更灵活 | 较粗粒度 |
| 性能 | 在高并发场景下通常更优 | 简单场景表现较好 |
| 显式unlock()方法 | 必须手动调用 | 自动管理 |
五、ReentrantLock的使用场景
1. 高并发场景
在高并发系统中,ReentrantLock的性能和灵活性使其成为首选。例如:
- 热点数据的读写操作。
- 复杂的业务逻辑需要更精细的锁控制。
2. 公平性要求高的场景
如果你需要严格的线程调度顺序(比如银行转账中的排队),可以使用公平锁。
3. 需要可重入性的场景
在方法嵌套调用中,如果多个方法都需要加锁,ReentrantLock的可重入性可以避免死锁问题。
六、ReentrantLock的注意事项
1.避免死锁
虽然ReentrantLock支持可重入性,但如果使用不当,仍然可能导致死锁。例如:
publicclassDeadlock{privatefinalReentrantLocklockA=newReentrantLock();privatefinalReentrantLocklockB=newReentrantLock();publicvoidmethodA(){lockA.lock();try{// 一些操作methodB();}finally{lockA.unlock();}}publicvoidmethodB(){lockB.lock();try{// 一些操作methodA();}finally{lockB.unlock();}}}如果methodA()和methodB()同时持有锁,可能会导致死锁。因此,在设计锁的使用顺序时要格外小心。
2.防止资源泄漏
忘记释放锁会导致资源泄漏,从而引发严重的问题。所以一定要在finally块中释放锁:
lock.lock();try{// 执行业务逻辑}finally{lock.unlock();}3.避免使用过多的锁
虽然ReentrantLock功能强大,但过度使用会导致系统性能下降。要合理设计锁的粒度,尽量减少锁的持有时间。
七、实战案例:实现一个简单的银行转账
为了让大家更好地理解ReentrantLock的应用场景,我们来写一个小例子——银行转账。
案例描述:
两个账户A和B,各有一定金额。我们需要从A转100元到B,并确保整个过程是线程安全的。
实现代码:
publicclassBankTransfer{privatefinalReentrantLocklock=newReentrantLock();privateMap<String,Integer>accounts=newHashMap<>();publicBankTransfer(){accounts.put("A",1000);accounts.put("B",500);}publicvoidtransfer(Stringfrom,Stringto,intamount){lock.lock();try{if(accounts.get(from)>=amount){accounts.put(from,accounts.get(from)-amount);accounts.put(to,accounts.get(to)+amount);System.out.println("Transfer completed. From: "+from+", To: "+to+", Amount: "+amount);}else{System.out.println("Insufficient balance in account "+from);}}finally{lock.unlock();}}publicstaticvoidmain(String[]args){BankTransfertransfer=newBankTransfer();// 创建多个线程进行转账操作for(inti=0;i<5;i++){finalintamount=100;newThread(()->transfer.transfer("A","B",amount)).start();}}}分析:
- 我们使用ReentrantLock来保证转账操作的原子性。
- 每次转账前,先检查账户余额是否足够,然后进行扣款和入账。
- 使用
finally块确保锁总是被释放。
八、总结
- ReentrantLock是一个功能强大的锁工具,支持可重入性和公平性选择。
- 在高并发场景下,它比
synchronized更灵活且性能更好。 - 使用时需要注意避免死锁和资源泄漏问题,并合理设计锁的粒度。
希望这篇长文能帮助你更好地理解和使用ReentrantLock!如果还有疑问或者想深入探讨的地方,欢迎随时交流。
📚 领取 | 1000+ 套高质量面试题大合集(无套路,闫工带你飞一把)!
成体系的面试题,无论你是大佬还是小白,都需要一套JAVA体系的面试题,我已经上岸了!你也想上岸吗?
闫工精心准备了程序准备面试?想系统提升技术实力?闫工精心整理了1000+ 套涵盖前端、后端、算法、数据库、操作系统、网络、设计模式等方向的面试真题 + 详细解析,并附赠高频考点总结、简历模板、面经合集等实用资料!
✅ 覆盖大厂高频题型
✅ 按知识点分类,查漏补缺超方便
✅ 持续更新,助你拿下心仪 Offer!
📥免费领取👉 点击这里获取资料
已帮助数千位开发者成功上岸,下一个就是你!✨