簡介
本文介紹synchronized
關鍵字的兩個重要的性質可重入性和不可中斷性,我們將用代碼來實現和驗證這兩個性質。
明白這種理論性質有什麼用?
明白了特性你才能懂得Synchronized
的作用範圍,使用的時候纔不容易犯錯。
一.可重入性
可重入性:同一個線程的外層函數獲得鎖後,內存函數可以直接獲取該鎖。
舉個生活中的可重入性的例子:
當我們排隊的時候,經常遇到有個別不文明的人,他們會讓自己的好友直接插隊在他的位置,而且有時還一次插隊好幾個。這就是一個人獲得了優先權後,會讓投靠他的所有人都獲得該權利,這就是一種可重入性的體現,所謂一人得道,雞犬升天。
1.Synchronized可重入性的兩個優點:
- 避免死鎖
避免死鎖的原因: 如果synchronized
不具備可重入性,當一個線程想去訪問另一個方法時,它自身已經持有一把鎖,而且還沒有釋放鎖,又想獲取另一個方法的鎖,於是造成了永遠等待的僵局,就會造成死鎖。有了可重入性後,自己持有一把鎖,並且可以直接進入到內層函數中,就避免了死鎖。
- 提升封裝性
編程人員不需要手動加鎖和解鎖,統一由JVM管理,提高了可利用性。
2.Synchronized可重入性的粒度(作用範圍)
Synchronized可重入性的作用範圍是整個獲得鎖的線程,線程內部的所有被調用的方法都共享該鎖。
線程內部的哪些方法共享同一把鎖?
- 同一個類中的同一個方法
- 同一個類中的不同的方法
- 不同類中的方法
3.代碼驗證上述三種情況的可重入性
①同一個類中的同一個方法,驗證可重入性:
實驗目的:通過遞歸反覆調用同一個方法,若被synchronized
修飾的方法,可以遞歸調用自己的方法,則表示在同步方法中具有可重入性。
public class RecursiveLockCondition1 {
//跳出遞歸的條件,需在遞歸方法外部聲明,否則會造成死循環
int i = 0;
private synchronized void method() {
System.out.println("遞歸調用:i=" + i);
if (i == 0) {
i++;
//遞歸調用本方法
method();
}
}
public static void main(String[] args) {
RecursiveLockCondition1 recursiveLockCondition1 = new RecursiveLockCondition1();
recursiveLockCondition1.method();
}
}
執行結果:
遞歸調用:i=0
遞歸調用:i=1
②同一個類中的不同的方法,驗證可重入性:
現象:從一個同步方法中,調用同一個類中的另一個同步方法是可以被執行的。證明:
同一個類中,一個同步方法調用另一個同步方法,可重入性質依然存在。
public class RecursiveLockCondition2 {
private synchronized void method1() {
System.out.println("method1執行");
method2();
}
private synchronized void method2() {
System.out.println("method2執行");
}
public static void main(String[] args) {
RecursiveLockCondition2 condition2 = new RecursiveLockCondition2();
condition2.method1();
}
}
預期和實際結果:method2()方法被執行。
method1執行
method2執行
③不同類中的方法,驗證可重入性:
使用子類調用父類中的同步方法,證明:
調用不同類中的同步方法也是可重入的。
public class RecursiveLockCondition3 {
protected synchronized void method() {
System.out.println("父類方法,被執行");
}
}
// 子類
class ChildClass extends RecursiveLockCondition3 {
@Override
protected synchronized void method() {
System.out.println("子類方法,被執行");
super.method();
}
public static void main(String[] args) {
ChildClass childClass = new ChildClass();
childClass.method();
}
}
預期和實際結果:父類的method()方法也被執行。
子類方法,被執行
父類方法,被執行
二.不可中斷性
定義:
當鎖被別的線程獲得以後,如當前線程想獲得,只能等待或堵塞,直到其他線程釋放了這個鎖。如果其他線程不釋放,當前線程會一直等待下去。
代碼證明:synchronized是不會自動中斷的。即synchronized具有不可中斷性。
代碼模擬:一個獲得鎖線程如果沒有執行完畢,另一個線程會一直等待下去。
public class UninterruptedCondition implements Runnable {
static UninterruptedCondition instance = new UninterruptedCondition();
@Override
public void run() {
method();
}
private synchronized void method() {
//死循環:一直打印當前線程名
while (true) {
System.out.println("線程名:" + Thread.currentThread().getName() + ",正在執行");
}
}
/**
*
*
*/
public static void main(String[] args) {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("執行結束");
}
}
預期和實際結果:只有一個線程在打印,另一個線程會一直等待下去。
線程名:Thread-1,正在執行
線程名:Thread-1,正在執行
線程名:Thread-1,正在執行
線程名:Thread-1,正在執行
線程名:Thread-1,正在執行
輸出相同,省略...
總結
本文從代碼層面分析和證明了synchronized
關鍵字的可重入性和不可中斷性,通過這些分析,我們主要想理解synchronized
的作用範圍和鎖的等待機制,如果這些概念在我們心中都是模糊的、不確定的,我們在使用的時候,也往往用的不放心,因爲多線程中的問題有時候達不到一定的併發量,即便有錯誤也是發現不了的,可能你本地起多個線程測試,也沒發現問題,一上線就出錯,所以我們對這些最常用的併發工具的執行機制和原理,一定要門兒清,才能在編碼時,就避免了很多問題的出現。喜歡本文,請收藏和點贊。