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加锁字符串内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决Spring Security的权限配置不生效问题

    解决Spring Security的权限配置不生效问题

    这篇文章主要介绍了解决Spring Security的权限配置不生效问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • tk.Mybatis 插入数据获取Id问题

    tk.Mybatis 插入数据获取Id问题

    本文主要介绍了tk.Mybatis 插入数据获取Id问题,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • java枚举enum,根据value值获取key键的操作

    java枚举enum,根据value值获取key键的操作

    这篇文章主要介绍了java枚举enum,根据value值获取key键的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • Spring Security6配置方法(废弃WebSecurityConfigurerAdapter)

    Spring Security6配置方法(废弃WebSecurityConfigurerAdapter)

    本文主要介绍了Spring Security6配置方法(废弃WebSecurityConfigurerAdapter),就像文章标题所说的,SpringSecurity已经废弃了继承WebSecurityConfigurerAdapter的配置方式,下面就来详细的介绍一下,感兴趣的可以了解一下
    2023-12-12
  • Java实现更新顺序表中的指定元素的示例

    Java实现更新顺序表中的指定元素的示例

    本文主要介绍了Java实现更新顺序表中的指定元素的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • SpringMVC实现文件上传与下载、拦截器、异常处理器等功能

    SpringMVC实现文件上传与下载、拦截器、异常处理器等功能

    这篇文章主要给大家介绍了关于SpringMVC实现文件上传与下载、拦截器、异常处理器等功能的相关资料,这些功能在我们日常开发中经常会遇到,本文通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-09-09
  • @ConfigurationProperties绑定配置信息至Array、List、Map、Bean的实现

    @ConfigurationProperties绑定配置信息至Array、List、Map、Bean的实现

    这篇文章主要介绍了@ConfigurationProperties绑定配置信息至Array、List、Map、Bean的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • Java实现鼠标拖拽移动界面组件

    Java实现鼠标拖拽移动界面组件

    在Java中,Frame或者JFrame自身已经实现了鼠标拖拽标题栏移动窗口的功能。但是Jframe的样式实在无法令人满意,那你又该怎么实现鼠标拖拽移动窗口的目的呢?今天我们来探讨下
    2014-09-09
  • Java实现超级实用的日记本

    Java实现超级实用的日记本

    一个用Java语言编写的,实现日记本的基本编辑功能、各篇日记之间的上下翻页、查询日记内容的程序。全部代码分享给大家,有需要的小伙伴参考下。
    2015-05-05
  • Dapr在Java中的服务调用实战过程详解

    Dapr在Java中的服务调用实战过程详解

    这篇文章主要为大家介绍了Dapr在Java中的服务调用实战过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06

最新评论