JAVA中Synchronized能否加鎖字符串詳解
1、簡(jiǎn)述
在 Java 開(kāi)發(fā)中,synchronized
是一種常見(jiàn)的同步機(jī)制,用于保證線程安全。但是你有沒(méi)有思考過(guò)這樣一個(gè)問(wèn)題:
“synchronized 可以給字符串(String)加鎖嗎?”
答案是:可以,但你應(yīng)該非常小心。
本文將深入剖析這個(gè)問(wèn)題,講清楚背后的機(jī)制、風(fēng)險(xiǎn),并給出實(shí)際建議。
2、synchronized 本質(zhì)上加的是什么鎖?
synchronized
實(shí)際上加的是對(duì)象鎖,也叫監(jiān)視器鎖(monitor lock)。也就是說(shuō):
synchronized (obj) { // 臨界區(qū) }
這段代碼表示:只有獲取到 obj
這個(gè)對(duì)象的監(jiān)視器鎖的線程才能進(jìn)入臨界區(qū)。
因此,只要是一個(gè)對(duì)象,包括字符串實(shí)例,理論上都可以被用作加鎖對(duì)象。
3、加鎖字符串——看似可行,實(shí)則隱患巨大
來(lái)看一個(gè)例子:
public class StringLockExample { public void doSomething(String lock) { synchronized (lock) { System.out.println(Thread.currentThread().getName() + " 獲得了鎖:" + lock); try { Thread.sleep(1000); } catch (InterruptedException ignored) {} } } }
啟動(dòng)多個(gè)線程調(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é)果是,兩個(gè)線程會(huì)串行執(zhí)行,因?yàn)榧拥氖峭粋€(gè)字符串 "LOCK"
的鎖。
但問(wèn)題來(lái)了:
字符串是不可變對(duì)象,且 JVM 對(duì)字符串常量具有“字符串池”優(yōu)化機(jī)制(String Interning)!
也就是說(shuō):
String a = "LOCK"; String b = "LOCK"; System.out.println(a == b); // true
兩個(gè)字符串變量實(shí)際上引用的是同一個(gè)對(duì)象。
因此,在你以為傳進(jìn)來(lái)的是不同的字符串時(shí),可能實(shí)際上加的是同一把鎖,或者反過(guò)來(lái)——你以為加的是同一把鎖,其實(shí)不是!
4、字符串加鎖的兩個(gè)典型陷阱
4.1 鎖粒度無(wú)法控制
如果你的鎖是這樣定義的:
synchronized ("user_" + userId)
你以為這是“每個(gè)用戶一個(gè)鎖”,但實(shí)際上由于字符串拼接會(huì)創(chuàng)建新對(duì)象,每次拼接都是一個(gè)新對(duì)象,鎖根本不會(huì)生效。
除非你手動(dòng) .intern()
:
synchronized (("user_" + userId).intern())
這又引入了新的問(wèn)題:intern 的對(duì)象存儲(chǔ)在字符串常量池中,頻繁使用可能會(huì)增加內(nèi)存壓力,甚至引發(fā)性能問(wèn)題。
4.2 外部可控鎖對(duì)象
如果你用外部傳入的字符串作為鎖對(duì)象,那你根本無(wú)法控制到底加的是什么鎖。惡意或不規(guī)范調(diào)用者可能傳入一個(gè)常量字符串、空字符串、甚至 null,導(dǎo)致同步行為混亂或拋出異常。
5、安全的替代方案
? 使用自定義鎖對(duì)象
最推薦的方式是自己定義一套鎖策略,例如使用 ConcurrentHashMap
管理鎖對(duì)象:
private final ConcurrentHashMap<String, Object> lockMap = new ConcurrentHashMap<>(); public void doSomething(String key) { Object lock = lockMap.computeIfAbsent(key, k -> new Object()); synchronized (lock) { // 臨界區(qū) } }
這種方式可以保證每個(gè)業(yè)務(wù) key 對(duì)應(yīng)一個(gè)明確的鎖對(duì)象,而且不會(huì)誤用常量字符串,鎖粒度清晰可控。
6、使用 Google Guava 的 Interner 實(shí)現(xiàn)更安全的字符串鎖
Interner
是 Google Guava 提供的一個(gè)實(shí)用工具類,用于實(shí)現(xiàn)“字符串實(shí)例的唯一化”。它的作用類似于 String.intern()
,但更靈活、可控,不依賴字符串常量池,避免了 JVM 層級(jí)的內(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 的線程會(huì)同步執(zhí)行 System.out.println("Processing key: " + key); } } }
Guava Interner 的優(yōu)勢(shì)
- 不污染 JVM 的字符串常量池(不像
String.intern()
)。 - 可以選擇 Weak 或 Strong 引用,避免內(nèi)存泄漏。
- 適合在緩存、去重、分布式任務(wù)分片等場(chǎng)景中鎖定“邏輯鍵”。
7、總結(jié)
并發(fā)編程中,鎖不是萬(wàn)能的,濫用鎖更是災(zāi)難。本文完整地分析了:
synchronized
是否能加鎖字符串(可以,但不推薦);- 字符串常量池帶來(lái)的鎖隱患;
- 如何使用
Object
、ConcurrentHashMap
構(gòu)建安全鎖; - 如何用 Guava 的
Interner
提供高效、可控的鎖機(jī)制; - 方法參數(shù)中加鎖字符串的風(fēng)險(xiǎn)及解決方案。
寫(xiě)高質(zhì)量的并發(fā)代碼,關(guān)鍵是理解鎖的語(yǔ)義、作用域和生命周期。希望這篇文章能幫你在并發(fā)之路上走得更穩(wěn)更遠(yuǎn)。
到此這篇關(guān)于JAVA中Synchronized能否加鎖字符串的文章就介紹到這了,更多相關(guān)JAVA Synchronized加鎖字符串內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決Spring Security的權(quán)限配置不生效問(wèn)題
這篇文章主要介紹了解決Spring Security的權(quán)限配置不生效問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03tk.Mybatis 插入數(shù)據(jù)獲取Id問(wèn)題
本文主要介紹了tk.Mybatis 插入數(shù)據(jù)獲取Id問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12java枚舉enum,根據(jù)value值獲取key鍵的操作
這篇文章主要介紹了java枚舉enum,根據(jù)value值獲取key鍵的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02Spring Security6配置方法(廢棄WebSecurityConfigurerAdapter)
本文主要介紹了Spring Security6配置方法(廢棄WebSecurityConfigurerAdapter),就像文章標(biāo)題所說(shuō)的,SpringSecurity已經(jīng)廢棄了繼承WebSecurityConfigurerAdapter的配置方式,下面就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2023-12-12Java實(shí)現(xiàn)更新順序表中的指定元素的示例
本文主要介紹了Java實(shí)現(xiàn)更新順序表中的指定元素的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06SpringMVC實(shí)現(xiàn)文件上傳與下載、攔截器、異常處理器等功能
這篇文章主要給大家介紹了關(guān)于SpringMVC實(shí)現(xiàn)文件上傳與下載、攔截器、異常處理器等功能的相關(guān)資料,這些功能在我們?nèi)粘i_(kāi)發(fā)中經(jīng)常會(huì)遇到,本文通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09@ConfigurationProperties綁定配置信息至Array、List、Map、Bean的實(shí)現(xiàn)
這篇文章主要介紹了@ConfigurationProperties綁定配置信息至Array、List、Map、Bean的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05Java實(shí)現(xiàn)鼠標(biāo)拖拽移動(dòng)界面組件
在Java中,F(xiàn)rame或者JFrame自身已經(jīng)實(shí)現(xiàn)了鼠標(biāo)拖拽標(biāo)題欄移動(dòng)窗口的功能。但是Jframe的樣式實(shí)在無(wú)法令人滿意,那你又該怎么實(shí)現(xiàn)鼠標(biāo)拖拽移動(dòng)窗口的目的呢?今天我們來(lái)探討下2014-09-09Java實(shí)現(xiàn)超級(jí)實(shí)用的日記本
一個(gè)用Java語(yǔ)言編寫(xiě)的,實(shí)現(xiàn)日記本的基本編輯功能、各篇日記之間的上下翻頁(yè)、查詢?nèi)沼泝?nèi)容的程序。全部代碼分享給大家,有需要的小伙伴參考下。2015-05-05Dapr在Java中的服務(wù)調(diào)用實(shí)戰(zhàn)過(guò)程詳解
這篇文章主要為大家介紹了Dapr在Java中的服務(wù)調(diào)用實(shí)戰(zhàn)過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06