本文目錄
- 故事緣由
- synchronized 關(guān)鍵字學(xué)習(xí)
- 什么是 synchronized ?
- synchronized 關(guān)鍵字的作用域
- 為什么要使用 synchronized 關(guān)鍵字啊易?
- synchronized 關(guān)鍵字的特性
- synchronized 同步鎖使用的優(yōu)化
故事緣由
前一陣面試完了 MSRA IEG 組的實習(xí),技術(shù)面試的時候由于我在時間內(nèi)提早寫完了代碼饮睬,所以面試官又加問了幾個問題租谈。其中一個是關(guān)于 Java 線程安全的,以前在工程上很少遇到這類問題捆愁,但是這類問題在面試的時候出現(xiàn)的概率是非常高的割去,題目不難,大概是這個樣子:
// 這段代碼是否是線程安全的昼丑?如果不是劫拗,怎么修改?
public class A {
int a = 0;
void funA(){
a++;
}
int funB(){
int b = a + 1;
return b;
}
}
這個題目是很典型的并發(fā)問題矾克,面試官叫我現(xiàn)場給他修改看看页慷,于是我把方法改成了:
public class A {
int a = 0;
synchronized void funA(){
a++;
}
synchronized int funB(){
int b = a + 1;
return b;
}
}
后來思考,感覺自己當時的回答其實不好胁附,這兩個方法里面酒繁,容易造成并發(fā)問題的其實只有一個變量 a
,考慮到程序的效率控妻, synchronized
關(guān)鍵字實際上是很重的州袒,對于這種情況可以直接使用 Java 里面自帶的一些原子類,在這里可以使用 AtomicInteger
弓候,改進的代碼如下:
public class A {
AtomicInteger a = new AtomicInteger(0);
void funA(){
a.incrementAndGet();
}
int funB() {
int b = a.addAndGet(1);
return b;
}
}
在面完這個問題之后郎哭,我覺得自己在 Java 并發(fā)編程上面的基礎(chǔ)仍然薄弱,還需要系統(tǒng)的學(xué)習(xí)和提升菇存,畢竟這個還是屬于編程語言的基礎(chǔ)夸研,對于面試來說,即使其他的算法解答和項目介紹得非常好依鸥,僥幸通過了面試亥至,但是基礎(chǔ)不牢固還是沒法在今后的工作和研究中走遠。
這幾日無聊贱迟,打算重新系統(tǒng)地對 Java 并發(fā)編程進行一個復(fù)習(xí)姐扮,同時記錄在此也算當作個人的筆記,如果有不足或者是錯誤的地方衣吠,歡迎大家指正茶敏。
synchronized 關(guān)鍵字學(xué)習(xí)
什么是 synchronized ?
在介紹這個關(guān)鍵字的時候,我想先說一個生活中的場景缚俏,假如有一個衛(wèi)生間惊搏,一次只能一個人使用贮乳,如果兩個人同時擠到衛(wèi)生間里面就會出問題 (誤)。有些代碼也是一樣胀屿,在多線程的環(huán)境下,如果多個線程同時調(diào)用了某段代碼包雀,這些代碼處理的結(jié)果在不同線程里面就有可能出現(xiàn)不同步的問題宿崭。所以,我們使用了 synchronized
才写,它是一個同步鎖葡兑,它的作用就是保證同一時間代碼調(diào)用的同步性。
換句話來說赞草,synchronized
就像是衛(wèi)生間包廂前面的鎖讹堤,一個人進去了以后,他拿到了這把鎖厨疙,把自己鎖在包廂里面洲守,這樣同一個時間就只有他能夠享用衛(wèi)生間了,沒有鎖的其他人是無法訪問這個衛(wèi)生間的沾凄,就只能在衛(wèi)生間門口排隊梗醇,等待里面的那個人上完衛(wèi)生間出來,釋放鎖撒蟀,把鎖交給下一個排隊的人叙谨。
那么,說了半天保屯,什么是鎖呢手负?(感覺很抽象)
我們先來閱讀一段代碼:
public class SynchronizedUse {
private int count = 10;
// 鎖對象
private Object o = new Object();
public void m() {
synchronized (o) { // 想要執(zhí)行下面一段代碼,必須先拿到 o 的鎖
count--;
System.out.println(Thread.currentThread().getName() + " count= " + count);
}
}
}
在這一段代碼里面姑尺,我們新建了一個 Object 對象竟终,并且使用 synchronized
作用在了了這個對象 o
上。很多人理解鎖概念的時候出現(xiàn)了誤解切蟋,認為 synchronized
“鎖”住的是 synchronized
作用的代碼塊衡楞,其實這是不對的,synchronized
鎖著的是對象敦姻。想要執(zhí)行上述代碼中被 synchronized
修飾的代碼塊瘾境,只有拿到對象o
的鎖才行。
由于對象只有一個镰惦,所以代碼保證了每次只會有一個線程能夠拿到這把鎖迷守,只有一個線程能夠執(zhí)行被鎖著的代碼。
synchronized 關(guān)鍵字的作用域
那我是不是每次想要同步一段代碼旺入,都得新建一個 Object 對象呢兑凿?
不是的凯力,閱讀下面這段代碼會發(fā)現(xiàn),其實我們可以直接使用 this
對象來進行代碼的鎖定礼华,這是一種簡化的寫法咐鹤。
public class SynchronizedUse02 {
private int count = 10;
private void m() {
synchronized (this) {
count--;
System.out.println(Thread.currentThread().getName() + " count= " + count);
}
}
}
synchronized
關(guān)鍵字除了使用花括號{}
修飾一個代碼塊(同步代碼塊)以外,還可以直接修飾一個方法圣絮,被修飾的方法也被稱為同步方法祈惶,其實和直接修飾一段代碼塊相同,修飾方法鎖定的也是 this
對象扮匠,如下面代碼所示捧请,這個寫法等同于SynchronizedUse02
(上一個demo)。
public class SynchronizedUse03 {
private int count = 10;
public synchronized void m() { // 等同于 synchronized(this){ ...}
count--;
System.out.println(Thread.currentThread().getName() + " count= " + count);
}
}
同時棒搜,synchronized
關(guān)鍵字還可以修飾一個靜態(tài)的方法疹蛉,其作用的范圍是整個靜態(tài)方法,作用的對象是這個類的所有對象力麸。了解 Java static
關(guān)鍵字的同學(xué)們都知道可款,作用在 static
方法上面的 synchronized
關(guān)鍵字實際上沒有作用在任何實例化的對象上,而是直接作用在類對象上面克蚂。如下面代碼所示:
public class SynchronizedUse04 {
private static int count = 10;
// synchronized 使用在靜態(tài)方法上的時候筑舅,相當于鎖定了 class
public synchronized static void m() {
count--;
System.out.println(Thread.currentThread().getName() + " count= " + count);
}
// 相當于這個方法
public static void mm() {
// 實際上是反射
synchronized (SynchronizedUse04.class) {
count--;
}
}
}
這么理解,即使我實例化了不同的SynchronizedUse04
對象陨舱,在不同的線程里面調(diào)用靜態(tài)方法 m()
翠拣,它仍然會保持同步,因為靜態(tài)方法是屬于類的游盲,而不是屬于對象的误墓,它對該類的所有對象都會保持同步。
為什么要使用 synchronized 關(guān)鍵字益缎?
之前說了那么多谜慌,我們一直都在強調(diào)一個"同步",那么為什么在高并發(fā)程序中莺奔,同步那么重要呢欣范,我們用一個小的demo來說明:
public class SynchronizedUse05 implements Runnable {
private int count = 10;
// 如果不加鎖,那么容易出現(xiàn)重復(fù)的數(shù)字,且得不到順序打印的數(shù)字令哟。
// 每個 synchronized 的代碼塊恼琼,都代表一個原子操作,是最小的一部分屏富,不可分晴竞。
public synchronized void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
/*
* main 方法里面,實際上是一個對象只啟動了一個方法狠半。
* 但是在 for 循環(huán)里面新建了多個線程來訪問一個對象 t 噩死。
* */
public static void main(String[] args) {
SynchronizedUse05 t = new SynchronizedUse05();
for (int i = 0; i < 8; i++) {
// 新建的8個線程都去訪問 t 里面的 run() 方法颤难。
new Thread(t, "THREAD" + i).start();
}
}
}
我們在這個例子里面啟動了8個不同的線程,每個線程都會調(diào)用t
里面的run()
方法已维,而每個線程都對變量count
進行了自減操作行嗤。源代碼為這個類的run()
方法加了同步鎖,如果把鎖去掉垛耳,我們就很容易在每次之中得到不同的運行結(jié)果栅屏,或者說出現(xiàn)重復(fù)的數(shù)字,且每次運行得不到順序打印的數(shù)字艾扮,如下圖所示既琴,我們可以這樣理解:
Thread 1
和 Thread 2
幾乎同時執(zhí)行代碼占婉,它們都拿到了值為10的count
并對其進行了修改泡嘴,所以這兩個線程就會輸出一樣的count
,之后由于線程對于資源的搶占式得到逆济,所以陸陸續(xù)續(xù)輸出結(jié)果的線程也不會是按照順序的,這也是為什么Thread 1
執(zhí)行完畢以后Thread 4
接下去執(zhí)行的原因了奖慌,而加了鎖之后可以消除這樣的問題抛虫。
不加鎖的運行結(jié)果 (每次未必一致):
THREAD5 count = 4
THREAD6 count = 3
THREAD7 count = 2
THREAD3 count = 6
THREAD2 count = 7
THREAD0 count = 9
THREAD1 count = 8
THREAD4 count = 5
加了鎖的運行結(jié)果 (運行結(jié)果每次一致):
THREAD0 count = 9
THREAD7 count = 8
THREAD6 count = 7
THREAD5 count = 6
THREAD4 count = 5
THREAD3 count = 4
THREAD2 count = 3
THREAD1 count = 2
同時,對多線程讀寫代碼加上同步鎖简僧,還可以避免常見的 “臟讀問題” (Dirty Read),所謂臟讀問題,指的是對業(yè)務(wù)寫方法加鎖乃坤,對業(yè)務(wù)讀方法不加鎖枫吧,那么同一個時間寫入的線程只能有一個甥捺,但是讀取卻不受限制镀层,這樣,在寫入的同時另外一個線程進行讀取备韧,就容易讀取到錯誤的數(shù)據(jù),輕則報出空指針異常,重則讀取到匪夷所思的錯誤數(shù)據(jù)疏旨。我們可以通過下面的這個demo來體會一下:
public class SynchronizedUse07 {
String name;
double balance;
private synchronized void set(String name, double balance) {
this.name = name;
// 寫入的時候加鎖谁榜,要是寫入時間中還在執(zhí)行一些其他的程序窃植,這時候讀程序在另外一個線程中
// 讀取信息帝蒿,寫入工作還沒完成巷怜,就容易讀取到錯誤的信息葛超。
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
private /* synchronized */ double getBalance(String name) {
return this.balance;
}
public static void main(String[] args) {
SynchronizedUse07 a = new SynchronizedUse07();
new Thread(() -> a.set("zhangsan", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
}
}
在上述代碼中暴氏,我們把讀取賬戶余額的方法 getBalance(String name)
上面的同步鎖 synchronized
注釋掉了,保留了寫入(初始化)方法的同步鎖绣张,這樣運行就會產(chǎn)生臟讀問題答渔。為了讓代碼問題突出,我們在set()
里面加入了一段延時程序:
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
在業(yè)務(wù)邏輯中侥涵,寫入數(shù)據(jù)可能是一個耗時操作(這里我們使用了2秒的延時),而如果讀取操作不上鎖沼撕,在寫入操作還未完成的時候就開始讀取,就會讀取到錯誤數(shù)據(jù)(這里我們在延時1秒后開始讀取芜飘,這時候?qū)懭氩僮魃形赐瓿桑┪癫颍缘谝淮挝覀冏x取到的數(shù)據(jù)是 0.0(未初始化值),而當寫入操作完成以后嗦明,我們再次進行讀取操作,這時候才讀取到正確的數(shù)據(jù) 100.0招狸。
程序輸出結(jié)果如下:
0.0 // 1秒的時候開始讀取敬拓,寫入未完成
100.0 // 3秒的時候讀取累榜,寫入(耗時約2秒)已完成
通過上面兩個例子猖凛,我們現(xiàn)在對 synchronized
關(guān)鍵字的作用和用法有了一個初步的了解。
synchronized 關(guān)鍵字的特性
synchronized 關(guān)鍵字修飾的同步方法能不能和非同步方法同時調(diào)用?
從之前臟讀問題的demo可以很輕易地得到答案:可以郊酒。
為此我們編寫了一個demo杠纵,感興趣的朋友可以嘗試運行體會一下:
public class SynchronizedUse06 {
// m1 是同步方法银亲,請問在執(zhí)行m1 的過程之中馏段,m2能不能被執(zhí)行晕翠? 回答:當然可以
private synchronized void m1() {
System.out.println(Thread.currentThread().getName() + " m1 start...");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end.");
}
private void m2() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m2.");
}
public static void main(String[] args) {
SynchronizedUse06 t = new SynchronizedUse06();
// new Thread(() -> t.m1(), "t1").start();
// new Thread(() -> t.m2(), "t1").start();
new Thread(t::m1, "t1:").start();
new Thread(t::m2, "t2:").start();
}
}
這里的m1()
是一個同步方法樊卓,其中我們?yōu)樗尤肓艘粋€長達10秒的延時七扰,m2()
是一個非同步方法。我們新建了兩個線程毕箍,線程t1
在運行方法m1()
的時候,我們啟動線程t2
運行方法m2()
涩澡,可以看到粥帚,m1()
和m2()
是可以同時運行的:
t1: m1 start...
t2: m2.
t1: m1 end.
在啟動線程這里我們使用了 Java8 的新特新:Lambda表達式,這樣的作用主要是簡化寫法(語法糖),感興趣的同學(xué)可以另外了解速警。
對于同步方法來說,它可以和非同步方法同時調(diào)用,那么對于同步方法之間的相互調(diào)用來說淮阐,這是否可行群扶?
我們先來看一段小程序:
/**
* synchronized 關(guān)鍵字的使用08
* 一個同步方法可以調(diào)用另外一個同步方法
* 一個線程已經(jīng)擁有某個對象的鎖,再次申請的時候仍然會得到該對象的鎖蒸走。
* 也就是說 synchronized 獲得的鎖是可以重入的。
*
* @author huangyz0918
*/
public class SynchronizedUse08 {
private synchronized void m1() {
System.out.println("m1 start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
}
private synchronized void m2() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2.");
}
public static void main(String[] args) {
SynchronizedUse08 t = new SynchronizedUse08();
t.m1();
}
}
運行結(jié)果:
m1 start...
m2.
在代碼中,我們調(diào)用了同步方法m1()
,m2()
也得到了調(diào)用礁哄,說明同步方法直接是可以相互調(diào)用的蚀乔。方法m1()
和m2()
需要的是同一把鎖,所以當m2()
在m1()
中被調(diào)用的時候衔峰,m1()
在持有鎖的前提下再次申請獲得這把鎖來執(zhí)行m2()
歇盼,這是可以的,因為synchronized
獲得的鎖是可以重入的帘不。
那么對于繼承的同步方法來說说莫,子類方法是否也是同步方法?
不是的寞焙,synchronized 關(guān)鍵字不能被繼承储狭。
雖然可以使用synchronized
來定義方法,但 synchronized
并不屬于方法定義的一部分捣郊,因此晶密,synchronized
關(guān)鍵字不能被繼承。如果在父類中的某個方法使用了 synchronized
關(guān)鍵字模她,而在子類中覆蓋了這個方法稻艰,在子類中的這個方法默認情況下并不是同步的,而必須顯式地在子類的這個方法中加上synchronized
關(guān)鍵字才可以侈净。當然尊勿,還可以在子類方法中調(diào)用父類中相應(yīng)的方法,這樣雖然子類中的方法不是同步的畜侦,但子類調(diào)用了父類的同步方法元扔,因此,子類的方法也就相當于同步了旋膳。具體可以見下面的 demo:
public class SynchronizedUse09 {
public static void main(String[] args) {
T t = new T();
t.m();
}
synchronized void m() {
System.out.println("m start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m end.");
}
}
class T extends SynchronizedUse09 {
@Override
synchronized void m() {
System.out.println("child m start...");
super.m();
System.out.println("child m end.");
}
}
這里的m()
顯式地加上了關(guān)鍵字synchronized
涮俄,所以它也是一個同步方法掀亥。如果不加這個關(guān)鍵字,m()
就不會得到同步,當然轴脐,在調(diào)用到父類super.m()
的時候,仍然需要獲得鎖才能夠執(zhí)行栖忠,否則方法將一直在super.m()
上面等待蝌戒。
那么對于一個程序,在獲得了鎖開始執(zhí)行的時候碱工,忽然在同步代碼塊里面出現(xiàn)了異常娃承,跳出了同步代碼奏夫,那么鎖是否會釋放?
程序在執(zhí)行的過程中历筝,如果出現(xiàn)異常酗昼,默認情況下鎖會被釋放。
我們可以試著建立一個 demo 研究一下:
public class SynchronizedUse10 {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + " start...");
while (true) {
count++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
int i = 1 / 0; // 此處拋出異常梳猪,鎖將被釋放麻削,若是不想鎖被釋放可以進行 catch 使循環(huán)繼續(xù)。
}
}
}
public static void main(String[] args) {
SynchronizedUse10 t = new SynchronizedUse10();
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(t::m, "t2").start();
}
}
運行結(jié)果:
t1 start...
t1 count = 1
t1 count = 2
t1 count = 3
t1 count = 4
t1 count = 5
t2 start...
Exception in thread "t1" java.lang.ArithmeticException: / by zero
t2 count = 6
at learn.multithreading.synchronizedlearn.SynchronizedUse10.m(SynchronizedUse10.java:32)
at java.base/java.lang.Thread.run(Thread.java:844)
t2 count = 7
t2 count = 8
t2 count = 9
t2 count = 10
t2 count = 11
t2 count = 12
這里我們使用了一個死循環(huán)舔示,在count == 5
地時候碟婆,我們手動的拋出了一個異常,使得線程t1
從同步方法m()
中跳出惕稻,并且釋放了鎖竖共,我們可以看到,在t1
拋出異常以后俺祠,t2
迅速獲得了鎖并且開始執(zhí)行公给。說明程序在執(zhí)行的過程中,如果出現(xiàn)異常蜘渣,默認情況下鎖會被釋放淌铐。 所以,在并發(fā)處理的過程中蔫缸,如果出現(xiàn)了異常一定要多加小心腿准,不然可能會發(fā)生不一致的情況。比如在一個 Web App 處理的過程中拾碌,多個 servlet 線程共同訪問一個資源吐葱,這時候如果異常處理不合適, 在第一個線程里面拋出異常校翔,其他線程就會進入同步代碼區(qū)弟跑,有可能會訪問到異常時產(chǎn)生的數(shù)據(jù)。
synchronized 同步鎖使用的優(yōu)化
雖然現(xiàn)在 Java 已經(jīng)對 synchronized
鎖進行了很大幅度的優(yōu)化防症,不過相對與其他同步機制來說孟辑,synchronized
的效率還是比較低的,所以在使用這個關(guān)鍵字的時候蔫敲,我們要注意鎖的粒度饲嗽,避免不必要的計算資源浪費。
舉個例子:
public class SynchronizedUse11 {
private int count = 0;
synchronized void m1() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++; // 業(yè)務(wù)邏輯中只有這句話需要同步燕偶,這時候不需要給整個方法上鎖喝噪。
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 業(yè)務(wù)邏輯中只有這一段語句需要同步,這時不應(yīng)該給整個方法上鎖
// 采用細粒度的鎖指么,可以使線程爭用的時間變短酝惧,從而提高效率。
synchronized (this) {
count++;
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在這里伯诬,我們只有count++
不符合原子性晚唇,所以容易出現(xiàn)同步問題,沒有必要對整個方法進行上鎖盗似,所以說synchronized
關(guān)鍵字的使用還是很靈活的哩陕,在編碼的時候要時時刻刻考慮到效率的問題。
同時赫舒,我們應(yīng)該理解synchronized
鎖定的本質(zhì)悍及,synchronized
其實鎖定的是堆內(nèi)存中的對象,所以當一個鎖定的對象的屬性發(fā)生了改變接癌,或者說心赶,鎖定對象的引用指向了堆內(nèi)存中的一個新的對象的時候,鎖也會改變缺猛。在實際使用當中缨叫,我們應(yīng)該避免發(fā)生這樣的情況:
public class SynchronizedUse12 {
public static void main(String[] args) {
SynchronizedUse12 t = new SynchronizedUse12();
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(t::m, "t2");
t.o = new Object(); // 鎖的對象改變,所以 t2 得以執(zhí)行荔燎,不然 t2 永遠得到不了執(zhí)行的機會耻姥。
t2.start();
}
Object o = new Object();
void m() {
synchronized (o) { // 本質(zhì)上說明了: 鎖的位置是鎖在堆內(nèi)存的對象上,而不是棧內(nèi)存對象的引用里面有咨。
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
}
輸出結(jié)果:
t1
t1
t2 // 從這里開始琐簇,t2線程也開始了運行
t1
t2
t1
t2
t1
....
在這個小程序里面,我們可以看到我們使用t.o = new Object();
將鎖的對象指向了堆內(nèi)存里面的一個新的對象座享,這個時候線程t2
其實已經(jīng)和t1
所需要的不是同一把鎖了婉商,所以線程t2
也開始運行。不然處于死循環(huán)方法m()
里面征讲,不等到t1
執(zhí)行完成据某,t2
永遠也得不到執(zhí)行的機會。
最后诗箍,我們在實際的開發(fā)過程中癣籽,經(jīng)常要盡力避免使用字符串對象進行鎖定。為什么呢滤祖?如果你使用了某個類庫筷狼,它鎖定了字符串對象 A ,這時候你在自己的源代碼里面又鎖定了字符串對象 A 匠童,兩段代碼不經(jīng)意之間使用了同一把鎖埂材,這樣就會出現(xiàn)很詭異的死鎖阻塞,而且對于你來說這樣的問題很難被排查出來汤求。并且對于字符串來說俏险,兩個相同的字符串其實指向的是同一個內(nèi)存地址严拒,所以看似使用的不是同一把鎖,實際上不然:
public class SynchronizedUse13 {
private String s1 = "Hello";
private String s2 = "Hello";
// 兩個字符串s1和s2實際上指向了同一個堆內(nèi)存的對象
// 棧內(nèi)存里面存放原始變量和對象的引用句柄
// 堆內(nèi)存里面存放的是對象的實例
void m1() {
synchronized (s1) {
}
}
void m2() {
synchronized (s2) {
}
}
}
總之竖独,Java 同步鎖 synchronized
的使用是很靈活的裤唠,需要在實踐中不斷總結(jié)和反復(fù)記憶。
相關(guān)閱讀:
本教程純屬原創(chuàng)莹痢,轉(zhuǎn)載請聲明
本文提供的鏈接若是失效請及時聯(lián)系作者更新