2012-01-10 104 views
6

我有一個線程類,它實現了runnable和一個int計數器作爲實例變量。兩個同步方法添加和子。當我以某種方式運行我的測試課時,它幾次都會打印錯誤的結果。據我所知,當一個方法被同步時,整個對象將被鎖定以供其他線程訪問,每次我們應該得到相同的結果,這個邏輯是正確的嗎?有些情況並非如此。我錯過了什麼嗎?與java線程不一致的結果

我的機器是Windows 7,64位。

public class ThreadClass implements Runnable { 

     int counter = 0; 

     @Override 
     public void run() { 
      add(); 
      sub(); 
     } 

     public synchronized void add() { 
      System.out.println("ADD counter" + (counter = counter + 1)); 
     } 

     public synchronized void sub() { 
      System.out.println("SUB counter" + (counter = counter - 1)); 
     } 
    } 

的TestClass

public class ThreadTest { 

    public static void main(String args[]) { 
     ThreadClass tc = new ThreadClass(); 
     Thread tc0 = new Thread(tc); 
     tc0.start(); 
     tc0.setPriority(Thread.MAX_PRIORITY); 
     Thread tc1 = new Thread(tc); 
     tc1.start(); 
     tc1.setPriority(Thread.NORM_PRIORITY); 
     Thread tc2 = new Thread(tc); 
     tc2.start(); 
     tc2.setPriority(Thread.MIN_PRIORITY); 
    } 
} 

結果

ADD counter1 
ADD counter2 
SUB counter1 
SUB counter0 
ADD counter1 
SUB counter0 

注:你可能需要做幾次試跑中產生這種矛盾。

+2

您應該將'add'方法中的printline改爲'ADD counter',這樣可以更容易地看到發生了什麼,並且可能還會爲每個線程提供id,以便您可以看到哪個輸出來自哪個線程。 – ChrisWue 2012-01-10 22:48:17

+0

@ChrisWue。我再次運行更新的System.out。感謝您指點。 – kosa 2012-01-10 23:01:53

回答

3

同步確實意味着所有線程都會阻止等待獲取鎖,然後才能進入同步塊。只有一個線程可以鎖定對象,因此只有一個線程可以在add()sub()方法中。

但是,這並不意味着有關線程排序的任何其他內容。你開始三個線程 - 唯一的保證是他們不會一次通過運行addsub方法跺腳。線程1可以調用add(),然後線程3可以調用add(),然後線程2可以調用add(),那麼他們可以全部調用sub()。或者他們都可以撥打add(),然後分別撥打sub()。或者任何混合 - 唯一的要求是每個線程在調用sub()之前調用add(),並且在另一個線程處於該方法中時,沒有兩個線程會調用add()sub()

另外:在某些情況下,它可能會在this上同步,因爲它是公開的 - 通常最好使用內部專用Object進行鎖定,以便其他呼叫者無法鎖定您的鎖並違反任何鎖定你設計的策略。

+0

我也試過私人對象鎖(我發佈的版本是最後一個版本的幾個,結果相同,現在我真正的問題是,任何帶有實例變量的程序都可以線程安全嗎?因爲優先級設置,它應該按順序執行,如果我需要確保以相同順序觸發的線程按相同順序執行,我該如何實現它? – kosa 2012-01-10 22:58:43

+1

@thinksteep:如果每個線程都必須等待另一個線程一個是在開始之前完成的嗎?你最終得到一系列可以在一個線程中執行的操作。 – 2012-01-10 23:01:06

+0

@thinksteep:是的,私人鎖確實會給你相同的結果。這是一個代碼風格的問題,你可能會希望要養成一個習慣,而不是一個bug,無論如何,優先級是一個**提示**,而不是一個保證,如果你需要確保一個特定的順序,你需要處理你自己的時間安排。 – 2012-01-10 23:05:16

5

您的結果看起來正確。

在執行這些方法的過程中,獲得對該對象的排他鎖定,但在add()sub()調用之間,線程可以自由交錯。

如果在所有線程運行後最終的總數爲0,那麼它們都不會覆蓋eathother,並且對counter的訪問已同步。

如果你想有counter只能去從01順序,不能擊打2,然後執行以下操作(這會使方法級同步冗餘,只要沒有其他班級都參與):

@Override 
public void run() { 
    synchronize(this) { 
     add(); 
     sub(); 
    } 
} 

但是,這使得線程的整個點無用,因爲您可以在單線程循環中執行該操作。

+0

我同意總共有0條語句,但如果我正確理解同步,我不應該得到2,因爲add/sub在最初鎖定對象的同一個線程中用於添加。如果沒有,在那裏使用Synchronized關鍵字的真正用途是什麼?你看到我在努力理解什麼? – kosa 2012-01-10 22:46:59

+0

當其中一個線程正在執行'run()'方法時,它不擁有該鎖('run()'不同步)。當它遇到'add()'調用時,它試圖獲取鎖。如果鎖不可用,則等待。一旦add()調用完成,它就釋放鎖。此時,另一個線程可能正在等待它,並在另一個線程的'sub()'有機會執行之前獲取鎖。這就是爲什麼兩個'add()'調用可以連續發生的原因。 – 2012-01-10 22:54:56

+0

儘管我同意你的解釋,但它沒有解釋計數器如何從​​2變爲0而不去1.爲什麼System.out.println重新排列行? – 2012-01-10 22:55:14

2

這兩組結果都沒有錯。它們與你的代碼的功能完全一致。不保證多線程的運行順序。

你「同步」的方法確保你得到有效的結果 - 實際上每次調用add增加了一個,實際上每次調用sub減去一個。沒有他們,你可以得到除零之外的最終結果。

+0

謝謝你的回答 – kosa 2012-01-11 05:49:10