Java走下神壇之同步代碼等同于斷面
同步經常作為斷面被引用。斷面是指一次只能有一個線程執行它。多個線程同時執行同步代碼是有可能的。
這個誤解是因為很多程序員認為同步關鍵字鎖住了它所包圍的代碼。但是實際情況不是這樣的。同步加鎖的是對象,而不是代碼。因此,如果你的類中有一個同步方法,這個方法可以被兩個不同的線程同時執行,只要每個線程自己創建一個的該類的實例即可。
參考下面的代碼:
運行SyncTest產生的輸出是1和3交叉的。如果printVal是斷面,你看到的輸出只能是1或者只能是3而不能是兩者同時出現。程序運行的結果證明兩個線程都在并發的執行printVal方法,即使該方法是同步的并且由于是一個無限循環而沒有終止。
要實現真正的斷面,你必須同步一個全局對象或者對類進行同步。下面的代碼給出了一個這樣的范例。
上面的類不再對個別的類實例同步而是對類進行同步。對于類Foo而言,它只有唯一的類定義,兩個線程在相同的鎖上同步,因此只有一個線程可以執行printVal方法。
這個代碼也可以通過對公共對象加鎖。例如給Foo添加一個靜態成員。兩個方法都可以同步這個對象而達到線程安全。
這個誤解是因為很多程序員認為同步關鍵字鎖住了它所包圍的代碼。但是實際情況不是這樣的。同步加鎖的是對象,而不是代碼。因此,如果你的類中有一個同步方法,這個方法可以被兩個不同的線程同時執行,只要每個線程自己創建一個的該類的實例即可。
參考下面的代碼:
| class Foo extends Thread { private int val; public Foo(int v) { val = v; } public synchronized void printVal(int v) { while(true) System.out.println(v); } public void run() { printVal(val); } } class SyncTest { public static void main(String args[]) { Foo f1 = new Foo(1); f1.start(); Foo f2 = new Foo(3); f2.start(); } } |
運行SyncTest產生的輸出是1和3交叉的。如果printVal是斷面,你看到的輸出只能是1或者只能是3而不能是兩者同時出現。程序運行的結果證明兩個線程都在并發的執行printVal方法,即使該方法是同步的并且由于是一個無限循環而沒有終止。
要實現真正的斷面,你必須同步一個全局對象或者對類進行同步。下面的代碼給出了一個這樣的范例。
| class Foo extends Thread { private int val; public Foo(int v) { val = v; } public void printVal(int v) { synchronized(Foo.class) { while(true) System.out.println(v); } } public void run() { printVal(val); } } |
上面的類不再對個別的類實例同步而是對類進行同步。對于類Foo而言,它只有唯一的類定義,兩個線程在相同的鎖上同步,因此只有一個線程可以執行printVal方法。
這個代碼也可以通過對公共對象加鎖。例如給Foo添加一個靜態成員。兩個方法都可以同步這個對象而達到線程安全。