JAVA中Synchronized能否加锁字符串详解

 更新时间:2025年07月04日 10:17:02   作者:拾荒的小海螺  
在Java中Synchronized可以锁任何对象,包括字符串,但由于字符串常量在字符串池中是共享的,所以如果多个线程使用相同的字符串作为锁对象,可能会导致意外的行为,这篇文章主要介绍了JAVA中Synchronized能否加锁字符串的相关资料,需要的朋友可以参考下

1、简述

在 Java 开发中,synchronized 是一种常见的同步机制,用于保证线程安全。但是你有没有思考过这样一个问题:

“synchronized 可以给字符串(String)加锁吗?”

答案是:可以,但你应该非常小心。

本文将深入剖析这个问题,讲清楚背后的机制、风险,并给出实际建议。

2、synchronized 本质上加的是什么锁?

synchronized 实际上加的是对象锁,也叫监视器锁(monitor lock)。也就是说:

synchronized (obj) {
    // 临界区
}

这段代码表示:只有获取到 obj 这个对象的监视器锁的线程才能进入临界区。

因此,只要是一个对象,包括字符串实例,理论上都可以被用作加锁对象

3、加锁字符串——看似可行,实则隐患巨大

来看一个例子:

public class StringLockExample {
    public void doSomething(String lock) {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " 获得了锁:" + lock);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ignored) {}
        }
    }
}

启动多个线程调用:

StringLockExample example = new StringLockExample();

Runnable task1 = () -> example.doSomething("LOCK");
Runnable task2 = () -> example.doSomething("LOCK");

new Thread(task1).start();
new Thread(task2).start();

结果是,两个线程会串行执行,因为加的是同一个字符串 "LOCK" 的锁。

但问题来了:

字符串是不可变对象,且 JVM 对字符串常量具有“字符串池”优化机制(String Interning)!

也就是说:

String a = "LOCK";
String b = "LOCK";
System.out.println(a == b); // true

两个字符串变量实际上引用的是同一个对象。

因此,在你以为传进来的是不同的字符串时,可能实际上加的是同一把锁,或者反过来——你以为加的是同一把锁,其实不是!

4、字符串加锁的两个典型陷阱

4.1 锁粒度无法控制

如果你的锁是这样定义的:

synchronized ("user_" + userId)

你以为这是“每个用户一个锁”,但实际上由于字符串拼接会创建新对象,每次拼接都是一个新对象,锁根本不会生效

除非你手动 .intern() :

synchronized (("user_" + userId).intern())

这又引入了新的问题:intern 的对象存储在字符串常量池中,频繁使用可能会增加内存压力,甚至引发性能问题。

4.2 外部可控锁对象

如果你用外部传入的字符串作为锁对象,那你根本无法控制到底加的是什么锁。恶意或不规范调用者可能传入一个常量字符串、空字符串、甚至 null,导致同步行为混乱或抛出异常。

5、安全的替代方案

✅ 使用自定义锁对象

最推荐的方式是自己定义一套锁策略,例如使用 ConcurrentHashMap 管理锁对象:

private final ConcurrentHashMap<String, Object> lockMap = new ConcurrentHashMap<>();

public void doSomething(String key) {
    Object lock = lockMap.computeIfAbsent(key, k -> new Object());

    synchronized (lock) {
        // 临界区
    }
}

这种方式可以保证每个业务 key 对应一个明确的锁对象,而且不会误用常量字符串,锁粒度清晰可控。

6、使用 Google Guava 的 Interner 实现更安全的字符串锁

Interner 是 Google Guava 提供的一个实用工具类,用于实现“字符串实例的唯一化”。它的作用类似于 String.intern(),但更灵活、可控,不依赖字符串常量池,避免了 JVM 层级的内存污染和性能隐患。

引入依赖:

<!-- Maven -->
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>32.1.1-jre</version>
</dependency>

使用示例:

import com.google.common.collect.Interner;
import com.google.common.collect.Interners;

public class GuavaInternerLock {
    private static final Interner<String> interner = Interners.newWeakInterner();

    public void doWork(String key) {
        String internedKey = interner.intern(key);
        synchronized (internedKey) {
            // 同样 key 的线程会同步执行
            System.out.println("Processing key: " + key);
        }
    }
}

Guava Interner 的优势

  • 不污染 JVM 的字符串常量池(不像 String.intern())。
  • 可以选择 Weak 或 Strong 引用,避免内存泄漏。
  • 适合在缓存、去重、分布式任务分片等场景中锁定“逻辑键”

7、总结

并发编程中,锁不是万能的,滥用锁更是灾难。本文完整地分析了:

  • synchronized 是否能加锁字符串(可以,但不推荐);
  • 字符串常量池带来的锁隐患;
  • 如何使用 ObjectConcurrentHashMap 构建安全锁;
  • 如何用 Guava 的 Interner 提供高效、可控的锁机制;
  • 方法参数中加锁字符串的风险及解决方案。

写高质量的并发代码,关键是理解锁的语义、作用域和生命周期。希望这篇文章能帮你在并发之路上走得更稳更远。

到此这篇关于JAVA中Synchronized能否加锁字符串的文章就介绍到这了,更多相关JAVA Synchronized加锁字符串内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JAVA防止重复提交Web表单的方法

    JAVA防止重复提交Web表单的方法

    这篇文章主要介绍了JAVA防止重复提交Web表单的方法,涉及Java针对表单的相关处理技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-10-10
  • java 生成有序账号的实现方法

    java 生成有序账号的实现方法

    下面小编就为大家带来一篇java 生成有序账号的实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-10-10
  • Java线程生命周期图文详细讲解

    Java线程生命周期图文详细讲解

    在java中,任何对象都要有生命周期,线程也不例外,它也有自己的生命周期。线程的整个生命周期可以分为5个阶段,分别是新建状态、就绪状态、运行状态、阻塞状态和死亡状态
    2023-01-01
  • java 实现MD5加密算法的简单实例

    java 实现MD5加密算法的简单实例

    这篇文章主要介绍了java 实现MD5加密算法的简单实例的相关资料,这里提供实例帮助大家应用这样的加密算法,需要的朋友可以参考下
    2017-09-09
  • Spring Boot项目中遇到`if-else`语句七种具体使用方法解析

    Spring Boot项目中遇到`if-else`语句七种具体使用方法解析

    当在Spring Boot项目中遇到大量if-else语句时,优化这些代码变得尤为重要,因为它们不仅增加了维护难度,还可能影响应用程序的可读性和性能,以下是七种具体的方法,用于在Spring Boot项目中优化和重构if-else语句,感兴趣的朋友一起看看吧
    2024-07-07
  • 探索Java中的equals()和hashCode()方法_动力节点Java学院整理

    探索Java中的equals()和hashCode()方法_动力节点Java学院整理

    这篇文章主要介绍了探索Java中的equals()和hashCode()方法的相关资料,需要的朋友可以参考下
    2017-05-05
  • 利用Spring Social轻松搞定微信授权登录的方法示例

    利用Spring Social轻松搞定微信授权登录的方法示例

    这篇文章主要介绍了利用Spring Social轻松搞定微信授权登录的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • springboot应用中使用过滤器的过程详解

    springboot应用中使用过滤器的过程详解

    过滤器通常用于实现跨切面的功能,例如身份验证、日志记录、请求和响应的修改、性能监控等,这篇文章主要介绍了springboot应用中使用过滤器,需要的朋友可以参考下
    2023-06-06
  • java实现Excel高性能异步导出的完整方案详解

    java实现Excel高性能异步导出的完整方案详解

    在大型电商系统中,数据导出是一个高频且重要的功能需求,本文将设计实现一套完整的Excel异步导出机制,通过注解驱动、任务队列、定时调度、消息通知等技术手段,完美解决了大数据量导出的技术难题,成为项目的重要技术亮点
    2025-10-10
  • Java中的Spring.factories文件详解

    Java中的Spring.factories文件详解

    文章主要介绍了在SpringBoot项目中如何使用spring.factories文件来注册项目依赖包中的bean,包括SpringFactories的实现原理、SpringBoot中的SPI机制、SpringFactories在SpringBoot中的应用、Beans的配置等内容,从而实现解耦的扩展机制,感兴趣的朋友一起看看吧
    2026-03-03

最新评论