2016-09-06 68 views
1

我想了解Java中的「synchronized block」。我寫了非常基本的代碼,看看如果我鎖定並更改thread_1中的對象並通過另一種方法從另一個thread_2(競爭條件)訪問它,會發生什麼情況。但是我很難理解這種行爲,因爲我期待Thread_1會先改變值,然後Thread_2會訪問新值,但結果並不如我預期的那樣。Java中的對象鎖

public class Example { 

public static void main(String[] args){ 

    final Counter counter = new Counter(); 

    Thread threadA = new Thread(new Runnable() { 

    @Override 
    public void run() { 
     System.out.println("THREAD_1_START"); 
     counter.add(1); 
     System.out.println("THREAD_1_END"); 
    } 
    }); 
    Thread threadB = new Thread(new Runnable() { 

    @Override 
    public void run() { 
     System.out.println("THREAD_2_START"); 
     System.out.println("GET_A_BY_THREAD_2:"+counter.getA(2)); 
     System.out.println("THREAD_2_END"); 

    } 
}); 
    threadA.start(); 
    threadB.start(); 
} 
} 

public class Counter{ 
String A = "NONE"; 

public void add(long value){ 
    synchronized (A) { 
     System.out.println("LOCKED_BY_"+value); 

     for(int i = 0; i < 1000000000; i++){} 

     setA("THREAD_"+value); 
     System.out.println("GET_A_BY_THREAD:"+getA(value)); 
    } 
} 

public void setA(String A){ 
     System.out.println("Counter.setA()"); 
     this.A = A; 
     System.out.println("Counter.setA()_end"); 
} 

public String getA(long value){ 
    System.out.println("Counter.getA()_BY_"+value); 
    return this.A; 
} 
} 

的輸出是:

THREAD_1_START 
THREAD_2_START 
LOCKED_BY_1 
Counter.getA()_BY_2 
GET_A_BY_THREAD_2:NONE 
THREAD_2_END 
Counter.setA() 
Counter.setA()_end 
Counter.getA()_BY_1 
GET_A_BY_THREAD:THREAD_1 
THREAD_1_END 

Thread_1鎖定 「A」 的字符串對象,並改變它,但Thread_2可以讀取的值在改變之前。當「A」處於鎖定狀態時,thread_2如何訪問「A」對象?

+0

除了已經給出的答案,您可以將'String a'標記爲'final',以確保同步對象保持不變。 –

回答

4

Thread_1鎖定 「A」 字符串對象,並改變它

沒有, 「NONE」 字符串對象Thread_1鎖,創建一個新的String對象,並使用一個參考覆蓋A場這個新對象。 Thread_2現在可以獲得其自由鎖定,但是在您當前的代碼中,getA方法甚至不嘗試獲取它。

您必須使用鎖定全部訪問,而不僅僅是寫入。因此getA也必須包含一個同步塊。

一般規則是從不使用可變實例字段進行鎖定。這樣做並沒有爲您提供有用的保證。因此,您的A字段應該是final,您必須刪除所有與此不相符的代碼。

+0

如果我刪除「setA(」THREAD _「+ value);」line code the code? – stackalreadyoverflowed

+0

也許這個問題稍後再編輯。但是Thread_1只設置一次A.經過由空循環引起的延遲。 –

+0

@JornVernee你說得對,我後來把這句話加到了我的答案中,現在我解開了。循環可能需要很短的時間,因爲只要它被JIT編譯,整個循環就會被當作死代碼。 –

1

你的代碼和你的解釋有很多錯誤。

  1. 同步塊只同步使用相同監視器的塊(在您的情況下爲A)。

    由於您的getter未同步,因此不受鎖的影響。 它不會被阻塞,可能在第一個線程持有鎖的時候運行,即使它在第一個線程釋放它之後執行鎖,它可能看不到第一個線程完成的更改。

    將同步添加到getter以解決此問題。

  2. 您鎖定A"NONE",但是然後將A更改爲指向不同的字符串。接下來的字符串會看到(實際上可能會看到)新引用,並鎖定新的字符串,因此您沒有兩個鎖定同一對象的同步塊。

    您幾乎總是希望鎖定包含同步塊的實例或其類上的實例。這正是您使用同步(靜態)方法而不是同步塊時所獲得的結果。

  3. 你的措辭,而且是很重要的:

    您鎖定對象A和改變計數器的實例。

    您無法鎖定對象,也無法更改字符串。您只能將引用更改爲指向新的字符串。