JAVA中Synchronized能否加鎖字符串詳解
1、簡述
在 Java 開發(fā)中,synchronized 是一種常見的同步機制,用于保證線程安全。但是你有沒有思考過這樣一個問題:
“synchronized 可以給字符串(String)加鎖嗎?”
答案是:可以,但你應(yīng)該非常小心。
本文將深入剖析這個問題,講清楚背后的機制、風險,并給出實際建議。

2、synchronized 本質(zhì)上加的是什么鎖?
synchronized 實際上加的是對象鎖,也叫監(jiān)視器鎖(monitor lock)。也就是說:
synchronized (obj) {
// 臨界區(qū)
}
這段代碼表示:只有獲取到 obj 這個對象的監(jiān)視器鎖的線程才能進入臨界區(qū)。
因此,只要是一個對象,包括字符串實例,理論上都可以被用作加鎖對象。
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) {}
}
}
}
啟動多個線程調(diào)用:
StringLockExample example = new StringLockExample();
Runnable task1 = () -> example.doSomething("LOCK");
Runnable task2 = () -> example.doSomething("LOCK");
new Thread(task1).start();
new Thread(task2).start();
結(jié)果是,兩個線程會串行執(zhí)行,因為加的是同一個字符串 "LOCK" 的鎖。
但問題來了:
字符串是不可變對象,且 JVM 對字符串常量具有“字符串池”優(yōu)化機制(String Interning)!
也就是說:
String a = "LOCK"; String b = "LOCK"; System.out.println(a == b); // true
兩個字符串變量實際上引用的是同一個對象。
因此,在你以為傳進來的是不同的字符串時,可能實際上加的是同一把鎖,或者反過來——你以為加的是同一把鎖,其實不是!
4、字符串加鎖的兩個典型陷阱
4.1 鎖粒度無法控制
如果你的鎖是這樣定義的:
synchronized ("user_" + userId)
你以為這是“每個用戶一個鎖”,但實際上由于字符串拼接會創(chuàng)建新對象,每次拼接都是一個新對象,鎖根本不會生效。
除非你手動 .intern() :
synchronized (("user_" + userId).intern())
這又引入了新的問題:intern 的對象存儲在字符串常量池中,頻繁使用可能會增加內(nèi)存壓力,甚至引發(fā)性能問題。
4.2 外部可控鎖對象
如果你用外部傳入的字符串作為鎖對象,那你根本無法控制到底加的是什么鎖。惡意或不規(guī)范調(diào)用者可能傳入一個常量字符串、空字符串、甚至 null,導(dǎo)致同步行為混亂或拋出異常。
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) {
// 臨界區(qū)
}
}
這種方式可以保證每個業(yè)務(wù) key 對應(yīng)一個明確的鎖對象,而且不會誤用常量字符串,鎖粒度清晰可控。
6、使用 Google Guava 的 Interner 實現(xiàn)更安全的字符串鎖
Interner 是 Google Guava 提供的一個實用工具類,用于實現(xiàn)“字符串實例的唯一化”。它的作用類似于 String.intern(),但更靈活、可控,不依賴字符串常量池,避免了 JVM 層級的內(nèi)存污染和性能隱患。
引入依賴:
<!-- 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 的線程會同步執(zhí)行
System.out.println("Processing key: " + key);
}
}
}
Guava Interner 的優(yōu)勢
- 不污染 JVM 的字符串常量池(不像
String.intern())。 - 可以選擇 Weak 或 Strong 引用,避免內(nèi)存泄漏。
- 適合在緩存、去重、分布式任務(wù)分片等場景中鎖定“邏輯鍵”。
7、總結(jié)
并發(fā)編程中,鎖不是萬能的,濫用鎖更是災(zāi)難。本文完整地分析了:
synchronized是否能加鎖字符串(可以,但不推薦);- 字符串常量池帶來的鎖隱患;
- 如何使用
Object、ConcurrentHashMap構(gòu)建安全鎖; - 如何用 Guava 的
Interner提供高效、可控的鎖機制; - 方法參數(shù)中加鎖字符串的風險及解決方案。
寫高質(zhì)量的并發(fā)代碼,關(guān)鍵是理解鎖的語義、作用域和生命周期。希望這篇文章能幫你在并發(fā)之路上走得更穩(wěn)更遠。
到此這篇關(guān)于JAVA中Synchronized能否加鎖字符串的文章就介紹到這了,更多相關(guān)JAVA Synchronized加鎖字符串內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot項目中遇到`if-else`語句七種具體使用方法解析
當在Spring?Boot項目中遇到大量if-else語句時,優(yōu)化這些代碼變得尤為重要,因為它們不僅增加了維護難度,還可能影響應(yīng)用程序的可讀性和性能,以下是七種具體的方法,用于在Spring?Boot項目中優(yōu)化和重構(gòu)if-else語句,感興趣的朋友一起看看吧2024-07-07
探索Java中的equals()和hashCode()方法_動力節(jié)點Java學(xué)院整理
這篇文章主要介紹了探索Java中的equals()和hashCode()方法的相關(guān)資料,需要的朋友可以參考下2017-05-05
利用Spring Social輕松搞定微信授權(quán)登錄的方法示例
這篇文章主要介紹了利用Spring Social輕松搞定微信授權(quán)登錄的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
java實現(xiàn)Excel高性能異步導(dǎo)出的完整方案詳解
在大型電商系統(tǒng)中,數(shù)據(jù)導(dǎo)出是一個高頻且重要的功能需求,本文將設(shè)計實現(xiàn)一套完整的Excel異步導(dǎo)出機制,通過注解驅(qū)動、任務(wù)隊列、定時調(diào)度、消息通知等技術(shù)手段,完美解決了大數(shù)據(jù)量導(dǎo)出的技術(shù)難題,成為項目的重要技術(shù)亮點2025-10-10

