Java中Synchronized的可重入性和不可中斷性的分析和代碼驗證

簡介

本文介紹synchronized關鍵字的兩個重要的性質可重入性和不可中斷性,我們將用代碼來實現和驗證這兩個性質。

明白這種理論性質有什麼用?

明白了特性你才能懂得Synchronized的作用範圍,使用的時候纔不容易犯錯。

一.可重入性

可重入性:同一個線程的外層函數獲得鎖後,內存函數可以直接獲取該鎖。

舉個生活中的可重入性的例子:

當我們排隊的時候,經常遇到有個別不文明的人,他們會讓自己的好友直接插隊在他的位置,而且有時還一次插隊好幾個。這就是一個人獲得了優先權後,會讓投靠他的所有人都獲得該權利,這就是一種可重入性的體現,所謂一人得道,雞犬升天。

1.Synchronized可重入性的兩個優點:

  1. 避免死鎖

避免死鎖的原因: 如果synchronized不具備可重入性,當一個線程想去訪問另一個方法時,它自身已經持有一把鎖,而且還沒有釋放鎖,又想獲取另一個方法的鎖,於是造成了永遠等待的僵局,就會造成死鎖。有了可重入性後,自己持有一把鎖,並且可以直接進入到內層函數中,就避免了死鎖。

  1. 提升封裝性

編程人員不需要手動加鎖和解鎖,統一由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的作用範圍和鎖的等待機制,如果這些概念在我們心中都是模糊的、不確定的,我們在使用的時候,也往往用的不放心,因爲多線程中的問題有時候達不到一定的併發量,即便有錯誤也是發現不了的,可能你本地起多個線程測試,也沒發現問題,一上線就出錯,所以我們對這些最常用的併發工具的執行機制和原理,一定要門兒清,才能在編碼時,就避免了很多問題的出現。喜歡本文,請收藏和點贊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章