聊聊Java里的那把锁:ReentrantLock到底有多强大?

从ReentrantLock到AQS:揭秘非公平锁的线程排队与唤醒机制 本文深入剖析了Java并发包中ReentrantLock非公平锁的底层实现机制,重点解析了其核心AQS(AbstractQueuedSynchronizer)如何通过CLH队列管理线程排队、利用waitStatus状态协调线程的阻塞(park)与唤醒(unpark)。文章通过银行排队等生动比喻,揭示了非公平锁为何默认且高性能,并详细梳理了从加锁、入队、挂起到释放、唤醒的完整源码级流程。 阅读详情

平时写代码的时候,碰到多线程问题,你是不是第一反应就是加个synchronized了事?说实话,这招在大多数情况下确实够用。但当你真正遇到复杂的并发场景时,就会发现synchronized有点力不从心了。

今天咱们就来深入聊聊Java并发包里的那把更强大的锁——ReentrantLock。

先来说说它到底是个啥

简单来说,ReentrantLock是Java5之后提供的一种可重入的互斥锁。什么叫可重入?就是说同一个线程可以多次获取同一把锁,不会自己把自己给锁死了。

想象一下这个场景:你在一个加锁的方法里调用了另一个也需要同样锁的方法。要是不可重入,那就完蛋了,线程直接卡死在自己手里。但ReentrantLock很聪明,它记得这个锁是谁拿的,如果是自己人,就放心让你进去。

底层是怎么运作的?

说到原理,就不得不提AQS(AbstractQueuedSynchronizer),这可以说是Java并发包的灵魂所在。ReentrantLock的所有能力,都建立在AQS的基础之上。

AQS内部维护了一个状态变量state和一个等待队列。state为0表示锁没人占用,大于0表示被占用,而且数值正好记录着重入的次数。等待队列则是用来管理那些没抢到锁的线程,让它们乖乖排队。

// 简化的加锁流程
public void lock() {
    if (CAS操作成功) {
        // 拿到锁了,美滋滋
        setExclusiveOwnerThread(当前线程);
    } else {
        // 没拿到,进队列等着吧
        acquire(1);
    }
}

这里用到了CAS(Compare And Swap),这是个原子操作,保证同时只有一个线程能修改成功。没抢到锁的线程不会傻傻地空转,而是会被挂起,等锁释放时再被唤醒。

公平还是不公平,这是个问题

ReentrantLock有个很有意思的特性:它支持公平和非公平两种模式。

非公平锁就像挤公交车,谁力气大谁先上,不管你先来后到。线程来了直接尝试抢锁,抢不到再排队。这种方式吞吐量高,但可能造成后来者先得,也就是线程饥饿。

公平锁就像排队买票,讲究先来后到。新来的线程会先看看队列里有没有人在等,有的话就乖乖去队尾排队。

// 创建锁时的选择
ReentrantLock unfairLock = new ReentrantLock(); // 默认非公平
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁

那么在实际开发中该怎么选呢?如果你的场景中线程持有锁的时间很短,或者对吞吐量要求很高,用非公平锁通常效果更好。但如果担心某些线程长期抢不到锁,那就得用公平锁来保证公平性了。

来看看它在实际项目中能干什么

银行转账:避免死锁的智慧

做金融系统最怕的就是死锁。想象两个账户互相转账,如果处理不好,线程A锁了账户1等账户2,线程B锁了账户2等账户1,直接就死锁了。

用ReentrantLock可以这样解决:

public boolean transfer(Account from, Account to, BigDecimal amount) {
    // 按账户ID排序,总是先锁ID小的那个
    Account firstLock = from.id < to.id ? from : to;
    Account secondLock = from.id < to.id ? to : from;
    
    if (!firstLock.lock.tryLock()) {
        return false;
    }
    try {
        if (!secondLock.lock.tryLock(1, TimeUnit.SECONDS)) {
            return false;
        }
        try {
            // 执行转账逻辑
            return doTransfer(from, to, amount);
        } finally {
            secondLock.lock.unlock();
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return false;
    } finally {
        firstLock.lock.unlock();
    }
}

这里用了tryLock而不是直接lock,还设置了超时时间,这样即使真的发生死锁,也不会无休止地等下去。

缓存系统:条件变量的妙用

做缓存的时候,我们经常需要控制缓存的大小。当缓存满了,写入线程需要等待;当缓存空了,读取线程需要等待。这种场景用synchronized配合wait/notify也能做,但代码写起来很别扭。

用ReentrantLock的条件变量就优雅多了:

public class BoundedCache<K, V> {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    
    public void put(K key, V value) throws InterruptedException {
        lock.lock();
        try {
            while (cache.size() >= capacity) {
                // 缓存满了,等着
                notFull.await();
            }
            cache.put(key, value);
            // 通知等待的读取线程:现在有数据了
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }
    
    public V get(K key) throws InterruptedException {
        lock.lock();
        try {
            while (cache.isEmpty()) {
                // 缓存空了,等着
                notEmpty.await();
            }
            V value = cache.get(key);
            notFull.signal(); // 通知写入线程:有空间了
            return value;
        } finally {
            lock.unlock();
        }
    }
}

注意这里用的是while而不是if来检查条件,这是因为线程被唤醒后条件可能又发生了变化,需要重新检查。

游戏服务器:可重入的价值

在游戏服务器里,经常需要对玩家对象进行各种操作。比如玩家受到伤害,血量减少,如果血量降到0,触发死亡逻辑。

public class Player {
    private final ReentrantLock lock = new ReentrantLock();
    private int health = 100;
    
    public void takeDamage(int damage) {
        lock.lock();
        try {
            this.health -= damage;
            if (this.health <= 0) {
                // 注意:这里直接调用onDeath,不需要再次加锁
                onDeath();
            }
        } finally {
            lock.unlock();
        }
    }
    
    private void onDeath() {
        // 因为已经是锁的持有者,所以可以直接执行
        // 如果是不可重入锁,这里就会死锁
        System.out.println("玩家死亡,触发复活逻辑");
        // 复杂的死亡处理...
    }
}

这种可重入的特性让我们的代码写起来自然很多,不用时刻担心锁的嵌套问题。

几个使用中的小贴士

  1. 一定要在finally里解锁
  2. 这个看似简单,但很容易忘。我曾经就遇到过因为异常导致锁没释放,整个系统卡死的惨痛经历。
  3. tryLock比lock更友好
  4. 特别是用在可能会长时间等待的场景中,tryLock可以设置超时时间,避免线程无限期等待。
  5. 公平锁不是默认选择
  6. 除非真的有强制的顺序要求,否则先用非公平锁。在大多数情况下,非公平锁的性能要好很多。
  7. 条件变量要用while循环检查
  8. 记住这个模式:while (!condition) { condition.await(); }

最后说两句

说实话,在平时的开发中,synchronized确实能满足80%的需求。但当你需要更细粒度的控制,比如可中断的锁获取、超时机制、多个条件变量时,ReentrantLock的优势就体现出来了。

不过也要注意,能力越大责任越大。ReentrantLock用起来比synchronized复杂,需要手动加锁解锁,要是忘了解锁,那问题就大了。

所以我的建议是:先用synchronized,当它真的不够用时,再请出ReentrantLock这把利器。毕竟,合适的工具用在合适的场景,才是最好的编程实践。

你在项目中用过ReentrantLock吗?有没有什么有趣的经历或坑想要分享?欢迎在评论区聊聊你的想法!

【JavaEE】深入解析线程同步:从ReentrantLock到信号量的实战应用 本文深入探讨了JavaEE开发中的线程同步核心机制,重点解析了ReentrantLock与信号量(Semaphore)的实战应用。通过对比synchronized,详细阐述了ReentrantLock的可中断、公平锁及条件变量等高级特性,并介绍了信号量作为并发流量控制器在资源池管理与限流场景中的精妙用法。文章结合代码示例,提供了从互斥访问到并发控制的完整解决方案与避坑指南。 阅读详情

相关推荐

Java工程师如何应对多Agent协作框架选型:从LangGraph到自研架构的工程化思考

在当今软件工程领域,工作流引擎和分布式系统架构是构建复杂业务系统的核心技术基础。工作流引擎通过定义任务节点、状态流转和条件分支,实现了业务流程的可视化编排与自动化执行,其背后的有向图模型和状态机原理为处理异步、多步骤任务提供了可靠框架。随着大模型API的普及,AI Agent作为具备“思考”和“执行”能力的新型计算单元,其协作编排问题本质上可抽象为一种特殊的有向图工作流,这为传统后端工程师带来了新的技术价值:将成熟的软件工程方法论应用于AI驱动的业务流程自动化,实现从单体智能到群体智能的系统性跃迁。在实际应

weixin_33721344的博客 482

深入理解Java中的锁机制

锁机制在并发编程中非常关键,它帮助我们解决资源共享和线程安全的问题。记住,合理使用锁非常重要,过度使用或不当使用锁可能会导致性能问题甚至死锁。小黑希望通过这篇文章,咱们能对Java中的锁有一个全面的了解。不论是内部锁(synchronized)还是显式锁(如ReentrantLock),它们都有自己的使用场景和优缺点。最关键的是,咱们要根据实际情况选择合适的锁类型和策略。学习并发编程是一个不断实践和探索的过程。

宋小黑的博客 1126

高温环境下RS485通信故障分析与MOS管解决方案

RS485通信作为工业控制领域的基础总线技术,其可靠性直接影响系统稳定性。在高温等恶劣环境下,传统三极管开关电路会因温度特性导致通信故障。通过分析三极管Ube电压随温度变化的特性,揭示了电路失效的物理原理。相比之下,MOS管作为电压控制型器件,具有温度稳定性好、功耗低等优势,更适合工业环境应用。本文通过一个发动机舱内RS485通信故障的实际案例,详细对比了三极管与MOS管在高温环境下的性能差异,并给出了具体的电路改进方案。该案例对工业通信接口设计具有普遍参考价值,特别适用于需要应对高温、振动等挑战性环境的硬

weixin_42612405的博客 407

JavaEE线程安全实战:从ReentrantLock到信号量的深度解析与应用

本文深入解析了JavaEE高并发场景下的线程安全核心工具ReentrantLock与信号量。通过对比synchronized,详细阐述了ReentrantLock的公平锁、可中断、超时机制及条件变量等高级特性,并探讨了信号量在限流与资源池管理中的应用。文章结合实战场景,提供了清晰的选型指南与避坑建议,帮助开发者构建安全高效的多线程程序。

weixin_29313547的博客 323

泛微ecology9 利用字段值动态控制明细表显示与隐藏

本文详细介绍了在泛微ecology9中,如何利用WfForm.bindFieldChangeEvent监听字段变化,结合jQuery动态控制明细表显示与隐藏的实战方法。通过五步操作,可实现根据字段值(如报销类型)智能切换明细表,提升表单交互体验与开发效率,并确保PC与移动端完美兼容。

7z8x2c4v6b9的博客 585

Python自然语言处理 NLTK 库用法入门教程【经典】

@本文来源于公众号:csdn2299,喜欢可以关注公众号 程序员学府 本文实例讲述了Python自然语言处理 NLTK 库用法。分享给大家供大家参考,具体如下: 在这篇文章中,我们将基于 Python 讨论自然语言处理(NLP)。本教程将会使用 Python NLTK 库。NLTK 是一个当下流行的,用于自然语言处理的 Python 库。 那么 NLP 到底是什么?学习 NLP 能带来什么好处? ...

python函数基础教程 1万+

解锁Java并发编程:ReentrantLock的全方位解析

ReentrantLock就像是并发编程中的瑞士军刀,它的多功能性使得它在处理复杂的线程同步问题时显得尤为重要。希望这篇文章能够帮助你更好地理解和运用ReentrantLock。别忘了在评论区留下你的想法哦!

u012808915的博客 1252

避坑指南:Qgis等时圈分析中ORS插件Key申请与参数设置的常见错误

本文详细解析了QGIS等时圈分析中ORS插件从API密钥申请到参数优化的全流程避坑指南。针对密钥激活、出行模式选择、时空参数设置等七个高频故障点,提供了实用的调试方案和优化技巧,帮助用户高效完成空间可达性分析。

weixin_42546508的博客 304

JUC_锁是什么,Synchronized与Lock的区别

多个线程在操作一个资源类的时候,因为操作时间大致相同,异步执行,会造成数据的写入异常。锁就是解决这个问题的,锁的本质就是队列 我举个例子,你去吃饭,但不排队,都不排队,很多学生(多线程)一拥而上一个窗口(资源类),食堂大妈也不知道谁要的什么菜,就听到谁喊要什么菜,就打这个菜(线程不安全) 加锁就是指在窗口设置了一个拦截线,只有排队(队列)才能打饭,这样就保证了每个人打的菜才是自己想要的(线程安全)

我认不到你的博客 377

Angular 相关的知识点和项目汇总

Angular 知识点和项目汇总

weixin_45428910的博客 582

uniapp - [微信小程序 | 安卓苹果app] 详细实现接入天地图完整流程,提供显示地图、IP 属地定位 / 用户定位的城市名称、用户定位的经纬度超多功能(复制运行示例代码,详细注释及常见问题)

uni-app,微信小程序,安卓app,苹果app,App端,支付宝小程序,nvue,天地图,国家天地图api,接入使用天地图,uniApp小程序端接入天地图,uniapp安卓苹果app端接入天地图,微信小程序平台如何引入天地图,天地图Api使用教程,如何在uniapp里集成天地图,使用天地图获取当前定位,天地图api教程,天地图官方文档,天地图详细使用教程,Uniapp项目如何使用天地图,项目引入天地图配置及使用教程,使用天地图获取用户定位,天地图获取用户当前位置信息(经纬度),如何通过天地图拿到用户定位

🏆 前端领域优质创作者 1万+

深入理解Java并发编程中的synchronized关键字:从原理到实战优化

是Java提供的一个内置锁机制,用于实现线程同步,确保多个线程在访问共享资源时的互斥性。简单来说,它就像是一个房间的钥匙,一次只允许一个线程进入"房间"(临界区)执行代码。synchronized作为Java最基础的同步机制,虽然看起来简单,但背后有着复杂的优化逻辑。从偏向锁到轻量级锁再到重量级锁的升级过程,体现了JVM开发者在性能优化上的智慧。在实际开发中,我们需要根据具体场景选择合适的同步策略:对于简单的同步需求,synchronized足够好用;对于复杂的并发控制,可以考虑使用JUC包中的更高级工具。

weixin_64016139的博客 1038

CCAR-23《正常类飞机适航规定》和CCAR-25《运输类飞机适航规定》差异分析

通过详细比较CCAR-23和CCAR-25在适用范围、设计标准、性能要求、运行限制、安全冗余和环境适应性方面的差异,我们可以看出,CCAR-23适用于轻型飞机,开发和运营成本较低,设计要求相对简单。而CCAR-25适用于大规模运输飞机,设计、开发、测试和运营管理都需要更高的复杂性和投入。

qq_41854911的博客 3710

Java面试核心知识点:多线程、集合框架与Spring原理详解

面向对象编程是Java的核心思想,通过封装、继承和多态三大特性实现代码复用和扩展性。在并发编程领域,线程池技术通过资源复用显著提升系统性能,而JUC包中的CountDownLatch和CyclicBarrier则解决了复杂的线程同步问题。集合框架中ArrayList与LinkedList的底层实现差异直接影响数据操作效率,HashMap的线程安全问题则引出了ConcurrentHashMap的分段锁优化方案。Spring框架通过AOP实现横切关注点的模块化,其自动配置机制大大简化了开发流程。这些Java核心

weixin_34082695的博客 407

点云数据处理:体素滤波算法

体素滤波是一种简单而有效的点云数据处理方法,可用于去除离群点和平滑点云表面。体素滤波是一种常用的点云数据处理算法,用于去除离群点并平滑点云表面。在本文中,我们将介绍CloudCompare和PCL(Point Cloud Library)中的体素滤波算法,并提供相应的源代码示例。PCL是一个专门用于点云处理的开源库,提供了一系列高效的点云处理算法和工具。然后,我们创建了一个。方法执行体素滤波,并将滤波后的点云保存为"filtered_cloud.ply"文件。方法来执行体素滤波,指定体素大小为0.01。

ZyqfCss的博客 390

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

多线程关于锁的运用

多线程的锁 关于线程同步的问题 线程同步是为了解决并发问题的,由于同一进程的多个线程共享同一块存储空间的情况下会带来一些访问的冲突问题,所以为了保证数据在方法中被访问时的正确性,我们会提前加入锁机制最常用的一开始就是synchronized关键字,当线程获得对象的独占锁便独占资源,让其他线程必须等待,使用完毕后再释放锁。 在使用synchronized关键字会产生一些问题: 一个线程持有锁会导致其他此锁的线程挂起等待其释放锁 在多线程的竞争下,加锁和释放锁的操作会导致比较多的上下文切换和调度延时,

hzyzzz的博客 661

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

聊聊并发:(七)concurrent包之AbstractQueuedSynchronizer分析

前言 在上几章中,我们介绍了Java并发相关的基础知识,了解了并发中常用的关键字synchronized与volatile的使用,以及线程的基本知识,本章开始,我们开始了解一下concurrent包中相关类的使用,util.concurrent包是在JDK1.5以后引入的工具包,该包中提供了多种并发编程场景下使用的工具类,帮助我们构建编写更加强大的多线程程序。 一、concurrent包结...

wtopps的专栏 777

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

13分钟聊聊并发包中常用同步组件并手写一个自定义同步组件

上篇文章10分钟从源码级别搞懂AQS(AbstractQueuedSynchronizer)说到JUC并发包中的同步组件大多使用AQS来实现本篇文章通过AQS自己来实现一个同步组件,并从源码级别聊聊JUC并发包中的常用同步组件本篇文章需要的前置知识就是AQS,如果不了解AQS的同学可以看上一篇文章哈~阅读本篇文章大概需要13分钟为了更容易理解其他同步组件,我们先来使用AQS自己来实现一个常用的可重入锁AQS模板方法流程是固定的,我们主要只需要来实现它的尝试获取同步状态和尝试释放同步状态方法即可。

关注菜菜的后端私房菜,获取更多技术干货~ 141

确保线程安全?这几个方法让你轻松过Java面试!

在Java多线程开发中,线程安全是核心考点!本文将从同步机制、原子变量、不可变对象和锁优化等角度,深入剖析确保线程安全的关键技术与实战技巧。掌握这些方法不仅能让你轻松应对面试挑战,更能提升代码质量和系统稳定性。快来学习这些实用技巧吧!

yanpeng2580的博客 915

到底什么是重入锁,一次搞清楚!(不要麻烦了)

这个是 JDK @since 1.5 添加的一种颗粒度更小的锁,它完全可以替代 synchronized 关键字来实现它的所有功能,而且 ReentrantLock 锁的灵活度要远远大于 synchronized 关键字。从类结构图看出,ReentrantLock 实现了 Lock 接口,ReentrantLock 只是 Lock 接口的一个实现而已。它们都是 java.util.concurrent 包里面的内容(俗称 JUC、并发包),也都是 JDK 1.5 开始加入的。

java_xiaoo的博客 1975

Java核心基础:从JVM内存模型到并发编程的实战内功修炼

Java作为企业级应用的主流语言,其核心基础是构建稳健、高性能系统的基石。理解Java内存模型(JMM)是掌握并发编程的关键,它定义了线程间共享变量的访问规则,解决了可见性、原子性和有序性问题。通过深入分析JVM内存区域划分,如堆、方法区(Metaspace),开发者能够有效诊断和解决`OutOfMemoryError`等线上疑难杂症,这是性能调优与系统稳定的技术保障。在并发场景下,从`synchronized`锁的升级优化机制到`java.util.concurrent`工具包的无锁CAS实现,这些原理为

weixin_30399155的博客 395

JUC并发编程介绍,聊聊单线程和多线程

JUC并发编程介绍,聊聊单线程和多线程

2301_77899321的博客 255

聊聊保证线程安全的10个小技巧

.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rgba(51, 51, 51, 1) } .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .ma...

优快云_KONGlX的博客 164

搞笑程序员谢飞机的互联网大厂Java面试记

谢飞机是一名初入职场的程序员,今天他怀着忐忑的心情,来到了某互联网大厂参加Java开发工程师的面试。呃,就是用来存键值对的,它会计算键的哈希值,然后。希望大家通过谢飞机的面试经历,学习到一些Java核心知识点,提升自己的技术水平。那你知道ReentrantLock和synchronized的区别吗……是用来跑Java的,它有堆?Java多线程中,线程池的作用是什么?那再问一个,你了解过JVM的内存模型吗?简单聊聊Spring吧,Spring的核心有哪些。啊,好像有核心线程数和最大线程数?

qinqin__的博客 824
上一篇: 从一次启动失败深入剖析:Spring循环依赖的真相
下一篇: @Autowired 的Bug让我们白忙三天
程序员职业指南
博客等级 码龄4年 2139粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值