JAVA中Synchronized能否加鎖字符串詳解
1、簡述
在 Java 開發(fā)中,synchronized
是一種常見的同步機制,用于保證線程安全。但是你有沒有思考過這樣一個問題:
“synchronized 可以給字符串(String)加鎖嗎?”
答案是:可以,但你應(yīng)該非常小心。
本文將深入剖析這個問題,講清楚背后的機制、風(fēng)險,并給出實際建議。
2、synchronized 本質(zhì)上加的是什么鎖?
synchronized
實際上加的是對象鎖,也叫監(jiān)視器鎖(monitor lock)。也就是說:
synchronized (obj) { // 臨界區(qū) }
這段代碼表示:只有獲取到 obj
這個對象的監(jiān)視器鎖的線程才能進(jì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
兩個字符串變量實際上引用的是同一個對象。
因此,在你以為傳進(jìn)來的是不同的字符串時,可能實際上加的是同一把鎖,或者反過來——你以為加的是同一把鎖,其實不是!
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ù)中加鎖字符串的風(fēng)險及解決方案。
寫高質(zhì)量的并發(fā)代碼,關(guān)鍵是理解鎖的語義、作用域和生命周期。希望這篇文章能幫你在并發(fā)之路上走得更穩(wěn)更遠(yuǎn)。
到此這篇關(guān)于JAVA中Synchronized能否加鎖字符串的文章就介紹到這了,更多相關(guān)JAVA Synchronized加鎖字符串內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決Spring Security的權(quán)限配置不生效問題
這篇文章主要介紹了解決Spring Security的權(quán)限配置不生效問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03tk.Mybatis 插入數(shù)據(jù)獲取Id問題
本文主要介紹了tk.Mybatis 插入數(shù)據(jù)獲取Id問題,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12java枚舉enum,根據(jù)value值獲取key鍵的操作
這篇文章主要介紹了java枚舉enum,根據(jù)value值獲取key鍵的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02Spring Security6配置方法(廢棄WebSecurityConfigurerAdapter)
本文主要介紹了Spring Security6配置方法(廢棄WebSecurityConfigurerAdapter),就像文章標(biāo)題所說的,SpringSecurity已經(jīng)廢棄了繼承WebSecurityConfigurerAdapter的配置方式,下面就來詳細(xì)的介紹一下,感興趣的可以了解一下2023-12-12SpringMVC實現(xiàn)文件上傳與下載、攔截器、異常處理器等功能
這篇文章主要給大家介紹了關(guān)于SpringMVC實現(xiàn)文件上傳與下載、攔截器、異常處理器等功能的相關(guān)資料,這些功能在我們?nèi)粘i_發(fā)中經(jīng)常會遇到,本文通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09@ConfigurationProperties綁定配置信息至Array、List、Map、Bean的實現(xiàn)
這篇文章主要介紹了@ConfigurationProperties綁定配置信息至Array、List、Map、Bean的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05Dapr在Java中的服務(wù)調(diào)用實戰(zhàn)過程詳解
這篇文章主要為大家介紹了Dapr在Java中的服務(wù)調(diào)用實戰(zhàn)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06