1. 程式人生 > >多執行緒之執行緒安全關鍵字synchronized

多執行緒之執行緒安全關鍵字synchronized

    synchronized關鍵字,是多執行緒程式設計時保證執行緒安全使用非常廣泛的java知識。下面我們學習下synchronized的相關知識:

實現原理

    synchronized的實現原理是基於記憶體中的lock原則。記憶體模型中的變數讀寫有八個步驟也有八個原則,synchronized的實現是基於八個原則中的lock原則。將其實現在指令級別就是monitorenter和monitorexit。編寫測試用例如下圖1-1,採用同步塊的方式在testJJJ()方法中,將其編譯後執行javap -c MyService1.class,輸出如下圖1-2

                                                                                         圖 1-1

                                                                                              圖1-2

monitor是監控器,java虛擬就為每個物件生成一個監控器,並放置在物件頭中。每個執行緒需要獲取到該監控器才可以進入synchronized控制的範圍以內的程式碼進行執行,否則會被放置在一個監控器等待池中,直到佔有該監控器的執行緒執行完程式碼後釋放監控器後,系統會以一定的策略從等待池中的執行緒中取出一個執行緒去獲得該監控器,然後執行該段程式碼。

使用方法

    從使用位置上分為同步方法和同步塊。同步方法指的是synchronized關鍵字出現在方法的簽名上,如下:

同步塊,如下:

       同步方法是將整個方法作為一段同步程式碼處理,任何時候只有一個執行緒獲取了該物件鎖才可以執行這個方法,而同步塊是將{}中的程式碼作為一段同步程式碼處理的。因為同步雖然保證了執行緒的安全,但是降低了併發性,所以業務邏輯允許的範圍內,同步的程式碼範圍越小越好。

從使用的監控器來說,可以分為一般的物件和Class物件(雖然Class的父類也是Object,但是Class的類的物件我們可以認為是一類特殊的物件)。

    一般物件的監控器,只是對該物件下的所有例項同步方法起作用。Class物件的監控器是對該類的所有靜態方法也就是類方法起作用。示例如下:

public class Main implements Runnable {

	// private Boolean falg = false;
	public static void main(String[] args) {
		Main main = new Main();
		Thread aThread = new Thread(main);
		Thread bThread = new Thread(main);
		Thread cThread = new Thread(main);
		Thread dThread = new Thread(main);
		aThread.start();
		bThread.start();
		cThread.start();
		dThread.start();
	}

	@Override
	public void run() {
		hi();
	}

	public void hi() {

		System.out.println(Thread.currentThread().getName() + " run...");
		System.out.println(Thread.currentThread().getName() + "hi thread!");

	}

}

    說明:Main類實現了Runnable介面,重寫了run方法,該方法呼叫了hi方法,hi方法中有兩行列印輸出程式碼。

   1.沒有在hi方法中加synchronized:

    

結果分析:執行結果非執行緒安全的。

2.在hi方法中加synchronized:

public class Main implements Runnable {

	// private Boolean falg = false;
	public static void main(String[] args) {
		Main main = new Main();
		Thread aThread = new Thread(main);
		Thread bThread = new Thread(main);
		Thread cThread = new Thread(main);
		Thread dThread = new Thread(main);
		aThread.start();
		bThread.start();
		cThread.start();
		dThread.start();
	}

	@Override
	public void run() {
		hi();
	}

	public synchronized void hi() {

		System.out.println(Thread.currentThread().getName() + " run...");
		System.out.println(Thread.currentThread().getName() + "hi thread!");

	}

}

結果分析:

執行緒安全。

3.將執行緒的建立方式修改後程式碼如圖:

public class Main implements Runnable {

	// private Boolean falg = false;
	public static void main(String[] args) {
		Thread aThread = new Thread(new Main());
		Thread bThread = new Thread(new Main());
		Thread cThread = new Thread(new Main());
		Thread dThread = new Thread(new Main());
		aThread.start();
		bThread.start();
		cThread.start();
		dThread.start();
	}

	@Override
	public void run() {
		hi();
	}

	public synchronized void hi() {

		System.out.println(Thread.currentThread().getName() + " run...");
		System.out.println(Thread.currentThread().getName() + "hi thread!");

	}

}

執行結果如圖:

結果分析:執行結果不是執行緒安全的。原因是因為每個執行緒獲取的監控器都不同,所以相當於同步沒有起作用。只需要在hi方法中加上static關鍵字即可。

修改後執行結果如圖:

結果分析:synchronized修飾的static方法,則監控鎖為Main.class ,而該物件只有一個,並且所有Main calss的例項化物件公用一個類方法,所以同一時刻只有一個執行緒能進入該方法。如果將該同步方法修改為同步塊,並將監控的物件該為Main.class,則效果一樣,但是原理不一樣。

4.再次驗證如上結論,修改如下:

public class Main implements Runnable {

	// private Boolean falg = false;
	public static void main(String[] args) {
		Main main = new Main();
//		Thread aThread = new Thread(main);
//		Thread bThread = new Thread(main);
//		Thread cThread = new Thread(main);
//		Thread dThread = new Thread(main);
		Thread aThread = new Thread(new Main());
		Thread bThread = new Thread(new Main());
		Thread cThread = new Thread(new Main());
		Thread dThread = new Thread(new Main());
		aThread.start();
		bThread.start();
		cThread.start();
		dThread.start();
		
		
	}

	@Override
	public void run() {
		hi();
		bye();
	}

	public static synchronized void hi() {

		System.out.println(Thread.currentThread().getName() + " hi run...");
		System.out.println(Thread.currentThread().getName() + " hi thread!");

	}

	public synchronized void bye() {
		System.out.println(Thread.currentThread().getName() + " bye run...");
		System.out.println(Thread.currentThread().getName() + " bye run run...");
		System.out.println(Thread.currentThread().getName() + " bye thread!");

	}
}

 

每個執行緒中傳入不同的Main物件,線上程中呼叫的run方法中執行hi和bye方法,hi方法為靜態的,bye方法不是靜態的。執行後結果如下:

bye方法並非執行緒安全的,但是hi方法是執行緒安全的,雖然兩個方法都加了synchronized。如果給bye方法加上static關鍵字,則結果為執行緒安全的。

5.在Main類中新增例項方法test,並加上synchronized關鍵字,程式碼如下:

public class Main implements Runnable {

	// private Boolean falg = false;
	public static void main(String[] args) {
		Main main = new Main();
		Thread aThread = new Thread(main);
		Thread bThread = new Thread(main);
		Thread cThread = new Thread(main);
		Thread dThread = new Thread(main);
//		Thread aThread = new Thread(new Main());
//		Thread bThread = new Thread(new Main());
//		Thread cThread = new Thread(new Main());
//		Thread dThread = new Thread(new Main());
		aThread.start();
		bThread.start();
		cThread.start();
		dThread.start();
		
		
	}

	@Override
	public void run() {
		hi();
		bye();
		test();
	}

	public static synchronized void hi() {

		System.out.println(Thread.currentThread().getName() + " hi run...");
		System.out.println(Thread.currentThread().getName() + " hi thread!");

	}

	public synchronized void bye() {
		System.out.println(Thread.currentThread().getName() + " bye run...");
		System.out.println(Thread.currentThread().getName() + " bye run run...");
		System.out.println(Thread.currentThread().getName() + " bye thread!");

	}
	public synchronized void test() {
		System.out.println(Thread.currentThread().getName() + " test run...");
		System.out.println(Thread.currentThread().getName() + " test run run...");
		System.out.println(Thread.currentThread().getName() + " test thread!");

	}
}

執行後結果如下:

Thread-1 hi run...
Thread-1 hi thread!
Thread-3 hi run...
Thread-3 hi thread!
Thread-3 bye run...
Thread-3 bye run run...
Thread-3 bye thread!
Thread-3 test run...
Thread-3 test run run...
Thread-3 test thread!
Thread-1 bye run...
Thread-2 hi run...
Thread-2 hi thread!
Thread-0 hi run...
Thread-0 hi thread!
Thread-1 bye run run...
Thread-1 bye thread!
Thread-0 bye run...
Thread-0 bye run run...
Thread-0 bye thread!
Thread-0 test run...
Thread-0 test run run...
Thread-0 test thread!
Thread-2 bye run...
Thread-2 bye run run...
Thread-2 bye thread!
Thread-2 test run...
Thread-2 test run run...
Thread-2 test thread!
Thread-1 test run...
Thread-1 test run run...
Thread-1 test thread!

結果顯示為執行緒安全的,也就是說非static的執行緒安全方法,只能同時一個執行緒進入其中的一個方法。

6.將test方法中的同步關鍵字去掉,重新執行程式,會出現如下的結果:

Thread-0 hi run...
Thread-0 hi thread!
Thread-0 bye run...
Thread-3 hi run...
Thread-3 hi thread!
Thread-0 bye run run...
Thread-2 hi run...
Thread-2 hi thread!
Thread-0 bye thread!
Thread-1 hi run...
Thread-1 hi thread!
Thread-2 bye run...
Thread-0 test run...
Thread-2 bye run run...
Thread-2 bye thread!
Thread-2 test run...
Thread-2 test run run...
Thread-0 test run run...
Thread-2 test thread!
Thread-3 bye run...
Thread-0 test thread!
Thread-3 bye run run...
Thread-3 bye thread!
Thread-3 test run...
Thread-3 test run run...
Thread-1 bye run...
Thread-1 bye run run...
Thread-1 bye thread!
Thread-3 test thread!
Thread-1 test run...
Thread-1 test run run...
Thread-1 test thread!

也就是該test方法並非執行緒安全的,同一時刻有可能有多個執行緒同時進入該方法。例如如上日誌顯示,Thread-0在執行期間Thread-2進入了該方法。