synchronized關鍵字是java並發程式設計中常用的同步鎖,用來鎖住方法或程式碼區塊,鎖定程式碼區塊時可以是synchronized(this){}、synchronized(Object){}、synchronized(類別class) {}。
當鎖定的內容執行完或執行過程中拋出異常,才會自動釋放鎖定。
如果想要手動釋放鎖,需要呼叫鎖定的物件的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中文網其他相關文章!