当前位置:

首页 > 编程开发 > 如何在 Java 中通过 synchronized(this) 实现代码块级别的线程安全

如何在 Java 中通过 synchronized(this) 实现代码块级别的线程安全

synchronized(this)通过锁定当前实例实现同步,但在单例等共享场景下易引发全局串行、性能下降及死锁风险。它仅适用于生命周期可控且不暴露的实例。更安全的替代方案包括私有锁对象、读写锁或原子类。验证锁生效需借助jstack等工具,并注意类加载器差异可能导致的锁失效问题。

本文目录

    如何在 Ja va 中通过 synchronized(this) 实现代码块级别的线程安全

    如何在 Ja va 中通过 synchronized(this) 实现代码块级别的线程安全

    为什么 synchronized(this) 不能随便用

    先明确一点:synchronized(this) 确实能实现加锁,但它锁住的是当前实例对象本身。问题恰恰出在这里——一旦这个对象被外部持有、暴露或在特定框架(如Spring)中被复用,就可能引发意料之外的锁竞争,甚至直接导致死锁。

    举个典型的场景:在Spring管理的单例Bean中,如果多个业务方法都使用了 synchronized(this),那么这些方法实际上是在串行执行。表面上看代码是并发的,但性能瓶颈已经形成,排查起来还特别困难。

    常见的错误现象包括:抛出 IllegalMonitorStateException(通常是因为在同步块外调用了 wait() 或 notify())、线程莫名卡死、以及系统吞吐量断崖式下跌却没有任何错误日志。

    • 适用边界:它只适用于那些生命周期明确可控的场景,比如手动 new 出来的工具类实例,并且确保这个实例不会传递给其他模块使用。
    • 继承风险:如果类可能被继承,子类重写方法时也用了 synchronized(this),父类的锁行为可能会被意外覆盖或干扰。
    • 静态资源无效:它无法保护静态字段,因为 this 是实例级别的。保护静态资源需要改用 synchronized(ClassName.class)。
    synchronized(this) 不能随便用,因其锁的是当前实例对象,若该对象被外部持有、复用(如Spring单例Bean),会导致全局串行、性能骤降;易引发IllegalMonitorStateException、死锁或锁失效,仅适用于生命周期可控且不被继承/暴露的手动new实例。

    synchronized(this) 的正确写法和边界条件

    使用它的核心原则是:确保临界区只包含真正需要互斥的操作,并且锁的粒度要与业务语义严格对齐。

    以账户扣款为例,必须将“读取余额→校验→执行扣减→写回结果”这一整个操作链锁住,形成一个原子操作。如果只锁住最后一行赋值语句,前面的读取和校验步骤就可能被其他线程干扰,导致数据不一致。

    public void withdraw(double amount) {
        synchronized(this) { // 锁住当前 Account 实例
            if (balance >= amount) {
                balance -= amount; // 这里才是临界操作
            } else {
                throw new IllegalStateException("Insufficient funds");
            }
        }
    }
    • 锁对象必须稳定:锁对象(这里是 this)引用必须是 final 或不可变的。如果在构造函数中就将 this 发布出去(例如注册为监听器或启动新线程),可能导致锁在完全初始化前就被获取,从而失效。
    • 警惕外部调用:禁止在同步块内部调用外部可重入的方法(如回调函数或第三方SDK方法),否则可能因反向持锁而造成难以排查的死锁。
    • 避免循环内加锁:尽量不要在循环体内部使用 synchronized(this),尤其是当循环次数不确定时。这很容易演变成一种隐式的“长锁”,严重阻塞其他线程。

    比 synchronized(this) 更安全的替代方案

    坦白说,多数情况下,直接锁 this 并非最佳选择。一种更可控、更安全的做法是显式声明一个私有的锁对象,将锁的作用域严格隔离在类内部:

    private final Object lock = new Object();
    
    public void updateCache() {
        synchronized(lock) {
            cache.clear();
            cache.putAll(fetchFromDB());
        }
    }
    • 封装性优势:synchronized(lock) 将锁封装在类内部,外部代码无法干扰,也不依赖于对象本身的身份(identity)。
    • 区分读写场景:如果需要区分读操作和写操作,应考虑使用 ReentrantReadWriteLock,而不是试图用多个 synchronized(this) 块来生硬地组合。
    • 无锁化尝试:对于简单的计数器或状态更新,可以优先考虑 AtomicInteger、AtomicReference 等原子类。它们在底层通常采用CAS操作,无锁且性能更高。

    立即学习“Ja va免费学习笔记(深入)”;

    调试时怎么确认 synchronized(this) 真正在起作用

    代码写完了,怎么验证锁真的生效了?光看代码是不够的,必须结合运行时工具。JVM 提供的 jstack 命令可以抓取当前所有线程的状态快照。分析输出时,重点查找带有 - waiting to lock <0x...>(等待锁)和 - locked <0x...>(已持有锁)字样的行,它们清晰地揭示了线程的锁竞争关系。

    还有一个极易被忽略的细节:在使用了OSGi、热部署或某些模块化容器的环境中,同一个类可能被不同的 ClassLoader 加载。这时,看似相同的 this 对象,实际上可能分属不同的类副本,导致 synchronized(this) 根本无法锁住预期的目标对象。遇到这类架构,需要格外小心。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。