首頁 >Java >java教程 >synchronized關鍵字的使用

synchronized關鍵字的使用

(*-*)浩
(*-*)浩轉載
2019-09-06 16:02:142537瀏覽

synchronized關鍵字是java並發程式設計中常用的同步鎖,用來鎖住方法或程式碼區塊,鎖定程式碼區塊時可以是synchronized(this){}、synchronized(Object){}、synchronized(類別class) {}。

synchronized關鍵字的使用

當鎖定的內容執行完或執行過程中拋出異常,才會自動釋放鎖定。

如果想要手動釋放鎖,需要呼叫鎖定的物件的wait()方法釋放掉鎖定並且置於等待狀態,切換到其他執行緒運行,而notify()方法只是喚醒一個呼叫了該對象wait()方法的其他線程,但不會釋放鎖,選擇順序也不由程式碼控制,由虛擬機器實作。

因此,物件的wait()、notify()、notifyAll()方法只能是配合synchronized關鍵字使用的,來完成執行緒間的調度。

其中鎖定方法等同於synchronized(this){方法的所有程式碼作為程式碼區塊},如下:

public synchronized void test() {
...
}

等同於

public void test() {
synchronized (this) {
...
}
}

上面的範例鎖住的是該類別的對象,如果鎖住的是個靜態的方法,我們知道靜態方法是屬於類別的而不屬於對象的,所以,synchronized修飾的靜態方法鎖定的是這個類別的所有對象,即就算是兩個實例對象,只要他們都是這個類的,那都會鎖住。

public synchronized static void test() {
	...
}

等同於

public static void test() {
synchronized (所在类.class) {
...
}	
}

無論是鎖定方法還是鎖定程式碼區塊,無論鎖定程式碼區塊時的參考物件是什麼,只要記住一個原則就一目了然了,那就是當參考對象相同時,同步鎖才起作用,否則鎖不會互斥,可以並發執行。

synchronized(this)表示當前類別的物件實例相同時鎖起作用,synchronized(Object)表示該Object物件相同時鎖起作用,synchronized(類別class)表示當都是該class類別時鎖起作用。

舉一個簡單的例子:

public class TestController {
 
    public class Task implements Runnable{
		private String str;
		
		Task(String str){
			this.str=str;
		}
		
		@Override
		public void run() {
			synchronized (str) {
				try {
					Thread.sleep(3000l);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(str);
			}
		}
		
	}
	
	public static void main(String[] args) throws InterruptedException {
		TestController testController = new TestController();
		Thread thread1 = new Thread(testController.new Task("1"));
		Thread thread2 = new Thread(testController.new Task("1"));
		thread1.start();
		thread2.start();
	}
}

上述程式碼,參考物件str都是"1",在java中,String字串如果透過this.str="1"這樣的方式賦值就等於str=String.valueOf("1"),如果字串"1"之前已經初始化過,那就會直接拿之前的,所以是同一個物件。根據上面介紹的原則,那鎖就會起作用,所以結果是3秒之後輸出1,再過3秒再輸出1。

如果把thread2改成

Thread thread2 = new Thread(testController.new Task("2"));

這時參考對像一個是"1",另一個是"2",不是同一個對象,所以鎖不會互斥,就不會起作用,所以結果是3秒後幾乎同時輸出1和2。

以上都是多個執行緒同時呼叫同一個方法,如果呼叫不同的方法呢?

public class Test{
	
	public synchronized void m1(){
		System.out.println("m1 running...");
		try {
			Thread.sleep(3000l);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("m1 end");
	}
	
	public synchronized void m2(){
		System.out.println("m2 running...");
		System.out.println("m2 end");
	}
	
	public static void main(String[] args) {
		Test test = new Test();
		new Thread(new Runnable() {
			@Override
			public void run() {
				test.m1();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				test.m2();
			}
		}).start();
	}
	
}

上面程式碼的輸出結果是:

m1 running...
//过3秒
m1 end
m2 running...
m2 end

上面就說過synchronized修飾在方法上等同於synchronized(this){方法的所有程式碼作為程式碼區塊},而this就代表是對象,也就是說,第一個Thread得到的是test物件的鎖,因為物件都是同一個test,所以第二個Thread無法取得到鎖,而被阻塞。

把上面的範例改造成如下:

private String str = "1";
	
public void m1(){
	synchronized(str){
		System.out.println("m1 running...");
		try {
			Thread.sleep(3000l);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("m1 end");
	}
}
 
public void m2(){
	synchronized(str){
		System.out.println("m2 running...");
		System.out.println("m2 end");
	}
}

第一個Thread呼叫m1()時取得到的是物件str的鎖,第二個Thread呼叫m2()時也需要取得對象str的鎖,而且因為是同一個Test對象,所以兩個str也是同一個對象,所以第二個Thread會因為取得不到鎖而被阻塞,輸出結果和之前的例子一樣。

如果再把上面的例子改造成如下:

public class M1 {
	
	public void m(String str){
		synchronized (str) {
			System.out.println("m1 runing");
			try {
				Thread.sleep(3000l);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("m1 end");
		}
	}
 
}
public class M2 {
	
	public void m(String str){
		synchronized (str) {
			System.out.println("m2 runing");
			System.out.println("m2 end");
		}
	}
 
}
public class Test {
 
	public static void main(String[] args) {
		String str = "1";
		new Thread(new Runnable() {
			@Override
			public void run() {
				new M1().m(str);
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				new M2().m(str);
			}
		}).start();
	}
	
}

這次呼叫的方法在兩個類別裡面,但是結果和之前的兩個例子是一樣的,因為鎖住的都是傳進來的str對象,同一個對像只有一把鎖,第一個Thread拿了,第二個Thread就只能等待。

總結:

A. 無論synchronized關鍵字加在方法上還是物件上,如果它作用的物件是非靜態的,則它取得的鎖定是對象;如果synchronized作用的物件是一個靜態方法或一個類,則它所取得的鎖是對類,該類所有的物件同一把鎖。 

B. 每個物件只有一個鎖定(lock)與之相關聯,誰拿到這個鎖誰就可以執行它所控制的那段程式碼。 

C. 實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制

以上是synchronized關鍵字的使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除