synchronized詳解

synchronized關鍵字主要用於對普通方法,靜態方法和代碼塊java

synchronized用於代碼塊

package Synchronized;

public class Test02 {
	public void method1(){
		System.out.println("method 1 start");
		try{
			synchronized(this){
				System.out.println("method 1 execute");
				Thread.sleep(3000);
			}
			
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 1 end");
	}
	public void method2(){
		System.out.println("method 2 start");
		try{
			synchronized(this){
				System.out.println("method 2 execute");
				Thread.sleep(1000);
			}
			
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 2 end");
	}
	public static void main(String[] args){
		Test02 test = new Test02();
		new Thread(new Runnable(){
			public void run(){
				test.method1();
			}
		}).start();;
		new Thread(new Runnable(){
			public void run(){
				test.method2();
			}
		}).start();;
	}
}
輸出:
method 1 start
method 1 execute
method 2 start
method 2 execute
method 1 end
method 2 end

雖然線程1和線程2都進入了對應的方法開始執行,可是線程2在進入同步塊以前,須要等待線程1中同步塊執行完成。多線程

對如下代碼反編譯併發

public class T {
	//同步代碼塊,使用monitor監視器
	public void method3() {
		synchronized(this){
			System.out.println("Hello World!");
		}
	}
}

獲得高併發

Compiled from "T.java"
public class Synchronized.T extends java.lang.Object{
public Synchronized.T();
  Code:
   0:	aload_0
   1:	invokespecial	#8; //Method java/lang/Object."<init>":()V
   4:	return

public void method3();
  Code:
   0:	aload_0
   1:	dup
   2:	astore_1
   3:	monitorenter
   4:	getstatic	#15; //Field java/lang/System.out:Ljava/io/PrintStream;
   7:	ldc	#21; //String Hello World!
   9:	invokevirtual	#23; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   12:	aload_1
   13:	monitorexit
   14:	goto	20
   17:	aload_1
   18:	monitorexit
   19:	athrow
   20:	return
  Exception table:
   from   to  target type
     4    14    17   any
    17    19    17   any

}
 

每一個對象有一個監視器鎖(monitor)。synchronized同步代碼塊時,線程便會執行monitorenter指令時嘗試獲取monitor的全部權,過程以下:性能

一、若是monitor的進入數爲0,則該線程進入monitor,而後將進入數設置爲1,該線程即爲monitor的全部者。this

二、若是線程已經佔有該monitor,只是從新進入,則進入monitor的進入數加1.spa

3.若是其餘線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再從新嘗試獲取monitor的全部權。   線程

4.執行monitorexit的線程必須是objectref所對應的monitor的全部者。指令執行時,monitor的進入數減1,若是減1後進入數爲0,那線程退出monitor,再也不是這個monitor的全部者。其餘被這個monitor阻塞的線程能夠嘗試去獲取這個 monitor 的全部權。 對象

synchronized用於普通方法

//對普通方法同步
public class Test01 {
	public synchronized void method1(){
		System.out.println("method 1 start");
		try{
			System.out.println("method 1 execute");
			Thread.sleep(100);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 1 end");
	}
	public synchronized void method2(){
		System.out.println("method 2 start");
		try{
			System.out.println("method 2 execute");
			Thread.sleep(300);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 2 end");
	}
	public static void main(String[] args){
		Test01 test = new Test01();
		new Thread(new Runnable(){
			public void run(){
				test.method1();
			}
		}).start();;
		new Thread(new Runnable(){
			public void run(){
				test.method2();
			}
		}).start();;
	}
}
輸出:
method 1 start
method 1 execute
method 1 end
method 2 start
method 2 execute
method 2 end

 可見,線程2對test對象的method2方法的執行須要等待線程1對test對象的method1方法的執行結束後才能執行。blog

方法的同步並無經過指令monitorenter和monitorexit來完成,不過相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標示符來實現方法的同步的:當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其餘任何線程都沒法再得到同一個monitor對象。 其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需經過字節碼來完成。

synchronized用於靜態方法

package Synchronized;

public class Test03 {
	public synchronized static void method1(){
		System.out.println("method 1 start");
		try{
			System.out.println("method 1 execute");
			Thread.sleep(100);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 1 end");
	}
	public synchronized static void method2(){
		System.out.println("method 2 start");
		try{
			System.out.println("method 2 execute");
			Thread.sleep(300);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("method 2 end");
	}
	public static  void main(String[] args){
		Test03 test1 = new Test03();
		Test03 test2 = new Test03();
		new Thread(new Runnable(){
			public void run(){
				test1.method1();
			}
		}).start();;
		new Thread(new Runnable(){
			public void run(){
				test2.method2();
			}
		}).start();;
	}
}
輸出:
method 1 start
method 1 execute
method 1 end
method 2 start
method 2 execute
method 2 end

執行結果以下,對靜態方法的同步本質上是對類的同步(靜態方法本質上是屬於類的方法,而不是對象上的方法),因此即便test和test2屬於不一樣的對象,可是它們都屬於SynchronizedTest類的實例,因此也只能順序的執行method1和method2,不能併發執行。

總結

synchronized是經過軟件(JVM)實現的,簡單易用,即便在JDK5以後有了Lock,仍然被普遍地使用。

synchronized其實是非公平的,新來的線程有可能當即得到監視器,而在等待區中等候已久的線程可能再次等待,不過這種搶佔的方式能夠預防飢餓。

synchronized只有鎖只與一個條件(是否獲取鎖)相關聯,不靈活,後來Condition與Lock的結合解決了這個問題。

多線程競爭一個鎖時,其他未獲得鎖的線程只能不停的嘗試得到鎖,而不能中斷。高併發的狀況下會致使性能降低。ReentrantLock的lockInterruptibly()方法能夠優先考慮響應中斷。 一個線程等待時間過長,它能夠中斷本身,而後ReentrantLock響應這個中斷,再也不讓這個線程繼續等待。有了這個機制,使用ReentrantLock時就不會像synchronized那樣產生死鎖了。

相關文章
相關標籤/搜索