【Java】synchronized 關鍵字

Synchronized關鍵字解決的是多個線程之間訪問資源的同步性,synchronized關鍵字可以保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執行。
其他線程 必須等待當前線程執行完該方法 / 代碼塊後才能執行該方法 / 代碼塊(阻塞型)。

鎖
總是,如果一個方法或者一個代碼塊被設置對象鎖或者類鎖,那它執行之前就必須要獲得這個鎖,如果鎖被佔用,就等待這個鎖被釋放之後再去獲取。


寫一個測試類:

class SynTest {
	public void test1() {
		for (int i = 0; i < 5; i++) {
			System.out.println("*****"+i + ",test1*****");
		}
	}
	
	public void test2() {
		for (int i = 0; i < 5; i++) {
			System.out.println("====="+i + ",test2=====");
		}
	}
}

開兩個線程:

public class Test {
	public static void main(String[] args) {
		final SynTest st = new SynTest();
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				st.test1();
			}
		});
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				st.test2();
			}
		});
		t1.start();
		t2.start();
	}
}

那麼此時的執行順序是不確定的,每次運行都可能產生不同的結果。

// 運行結果1
=====0,test2=====
=====1,test2=====
*****0,test1*****
=====2,test2=====
=====3,test2=====
=====4,test2=====
*****1,test1*****
*****2,test1*****
*****3,test1*****
*****4,test1*****

// 運行結果 2
=====0,test2=====
*****0,test1*****
=====1,test2=====
=====2,test2=====
=====3,test2=====
=====4,test2=====
*****1,test1*****
*****2,test1*****
*****3,test1*****
*****4,test1*****


// 運行結果 3
*****0,test1*****
*****1,test1*****
*****2,test1*****
=====0,test2=====
*****3,test1*****
*****4,test1*****
=====1,test2=====
=====2,test2=====
=====3,test2=====
=====4,test2=====
對象鎖

用 synchronized 修飾實例方法 or 用 synchronized(this) 修飾代碼塊:

class SynTest {
	public synchronized void test1() {
		for (int i = 0; i < 5; i++) {
			System.out.println("*****"+i + ",test1*****");
		}
	}
	
	public void test2() {
		synchronized (this) {
			for (int i = 0; i < 5; i++) {
				System.out.println("====="+i + ",test2=====");
			}
		}
	}
}

main() 不變,運行結果:

*****0,test1*****
*****1,test1*****
*****2,test1*****
*****3,test1*****
*****4,test1*****
=====0,test2=====
=====1,test2=====
=====2,test2=====
=====3,test2=====
=====4,test2=====

兩個線程調用的是同一個類中的方法,且這兩個方法都設了對象鎖,當第一個線程運行時,會對這個實例上鎖,此時另一個線程要調用這個實例中的方法,必須要獲得該實例的鎖,於是它一直等到第一個線程運行結束後,鎖被釋放之後才能運行。
若將main() 方法做稍許修改,使線程1和線程2調用的不是同一個實例的方法,則此時兩線程互不影響。

類鎖

用 synchronized 修飾靜態方法 or 用 synchronized(this) 修飾代碼塊:

class SynTest {
	public static synchronized void test1() {
		for (int i = 0; i < 5; i++) {
			System.out.println("*****"+i + ",test1*****");
		}
	}
	
	public void test2() {
		synchronized (SynTest.class) {
			for (int i = 0; i < 5; i++) {
				System.out.println("====="+i + ",test2=====");
			}
		}
	}
}

public class Test {
	public static void main(String[] args) {
		SynTest st1 = new SynTest();
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				SynTest.test1();
			}
		});
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				st1.test2();
			}
		});
		t1.start();
		t2.start();
		
	}
}

運行結果:

*****0,test1*****
*****1,test1*****
*****2,test1*****
*****3,test1*****
*****4,test1*****
=====0,test2=====
=====1,test2=====
=====2,test2=====
=====3,test2=====
=====4,test2=====

test1()和test2()方法中的代碼塊都使用了類鎖。當線程1開始執行,SynTest 類鎖被佔用,在其執行結束之前,線程2因爲拿不到類鎖會一直等待,當線程1執行結束,類鎖被釋放,線程2佔用類鎖,開始執行 test2()方法。

修改如下:

class SynTest {
	public static synchronized void test1() {
		for (int i = 0; i < 5; i++) {
			System.out.println("*****"+i + ",test1*****");
		}
	}
	
	public void test2() {
		for (int i = 0; i < 3; i++) {
			System.out.println("#####"+i + ",test2#####");
		}
		synchronized (SynTest.class) {
			for (int i = 0; i < 3; i++) {
				System.out.println("====="+i + ",test2=====");
			}
		}
	}
}

main() 不變,運行結果:

// 前 8 行隨機組合,但後 3 行固定
#####0,test2#####
#####1,test2#####
*****0,test1*****
#####2,test2#####
*****1,test1*****
*****2,test1*****
*****3,test1*****
*****4,test1*****
=====0,test2=====
=====1,test2=====
=====2,test2=====
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章