2013-02-27 209 views
5

article談到Java的「synchronized」關鍵字。Java線程「同步」

... 
    private int foo; 
    public synchronized int getFoo() { return foo; } 
    public synchronized void setFoo(int f) { foo = f; } 

如果主叫方希望增加foo的屬性,下面的代碼,這樣做是不是線程安全的:

... 
    setFoo(getFoo() + 1); 

如果兩個線程試圖在同一時間遞增foo,那麼結果可能是foo的價值根據時間而增加1或2。

現在,我的問題:

上setFoo爲什麼不 「同步」()防止上述粗體 線?

回答

6

這是check-then-act競爭條件的一個例子。

情景可能發生類似如下:

Thread-1 getFoo() returns 0 
Thread-2 getFoo() returns 0 
Thread-2 setFoo(1) 
Thread-1 setFoo(1) 

這意味着,兩個線程試圖增加富,但它僅遞增一次的效果。

當其他答案已經確定時,同步增量與同一個對象上的同步塊鎖定getFoo()和setFoo()將阻止這種競爭條件,因爲線程將無法像上面那樣交錯。

6

,因爲您選擇保證沒有其他人越來越富與你們一起,並沒有其他人設置foo的背部,除了你,但你不是你叫之間保證沒人設法進入和退出(或只是) get()和你的呼喚()設置

你可以認爲代碼完全等同於這一點:

int temp = getFoo(); //safe method 
temp = temp+1; //not protected here - im not holding any locks ... 
setFoo(temp); //safe method 
4

在這兩種方法的​​關鍵字並不能使它線程安全的,因爲一個線程可能請致電getFoo,然後另一個線程可以調用getFoo,他們每個人都得到相同的結果。然後他們每個人都加1,然後撥打setFoo,最終結果是foo只會增加一次,而不是兩次。正如你的文章指出的,這是一個競賽條件

爲了使線程安全,讀取和寫入必須在同一個同步塊中,而不需要單獨的get和set方法。

public synchronized void addFoo(int addend) 
{ 
    foo += addend; 
} 
+0

'然後他們每個人都加一個並調用setFoo,最終的結果是foo只增加一次,而不是兩次。爲什麼? – 2013-02-27 18:15:07

+0

因爲每個線程都將'foo'更新爲相同的值。例如,他們每個人都得到值2,他們每個人都加1得到3,然後他們每個人都將值設置爲3. – rgettman 2013-02-27 18:19:43

+0

如果我理解正確,那麼你的陳述不應該是'那麼他們每個人都添加一個並調用setFoo,最終結果是foo獲得**兩次** **?' – 2013-02-27 18:45:18

1

代碼中的陷阱主要是它似乎getFoo將被稱爲「裏面」setFoo。的

setFoo(){ 
    //getFoo(); 
    //... 
} 

類這是不正確,因爲在現實中getFoo調用setFoo之前被調用。下面是示例,說明它:

public static int foo(int i) { 
    System.out.print("FOO!"); 
    return i; 
} 

public static int bar(int i) { 
    System.out.print("BAR!"); 
    return i; 
} 

public static void main(String[] args) throws Exception { 
    System.out.println(foo(bar(1))); 
} 

輸出:

BAR!FOO!1 

正如你可以看到barfoo之前調用。所以在你的情況下,有可能兩個(或更多)線程調用getFoo,它們會在調用setFoo之前返回當前值。在這種情況下,他們都將擁有相同的價值,可以說,0時,他們會打電話setFoo他們都將其設置爲1

0

做到這一點代碼幫助?

class C { 
    private int foo; 
    public int getFoo() { return foo; } 
    public void setFoo(int f) { foo = f; } 
} 

C myC = new C(); 
synchronized(myC) { 
    int foo = myC.getFoo(); 
    myC.setFoo(foo + 1); 
} 
println(myC.foo); 
+0

所以重點是, myC.setFoo(1)'可以在'println(myC.getFoo())'之前調用? – 2013-02-27 19:02:27