2011-10-07 102 views
2

爲什麼人們只爲一行代碼「同步」?什麼是「同步」?同步代碼塊

public final void addListener(Listener listener) { 
    synchronized (listeners) { 
    listeners.add(listener); 
    } 
} 

編輯:謝謝大家。非常好的答案!

+0

爲什麼(this)「一行代碼」比42行代碼更原子化?其中的原因在於:-) – 2011-10-08 00:49:25

回答

8

​​本身意味着如果多個線程試圖同時運行這段代碼,那麼在任何給定時間只有一個線程被允許在該塊內部。 synchronized (listeners)使用listeners作爲鎖定標識符,這意味着此限制適用於在該變量上同步的所有塊 - 如果一個線程位於其中一個塊內,則其他線程不能進入其中任何一個線程。

即使在一個塊中只有一個函數調用,這仍然有意義:該函數由許多其他指令組成,並且控制可能切換到另一個線程,而第一個函數處於該函數的中間功能。如果函數不是線程安全的,則可能導致問題,如數據被覆蓋。

在這種特殊情況下,函數調用包括向集合listeners添加值。儘管創建線程安全的集合並非不可能,但大多數集合對於多個編寫者來說都不是線程安全的。因此,爲了確保收集不會混亂,需要​​。

編輯:爲了讓事情可怎麼搞的一團糟一個例子,假設這個簡化的實施add,其中length是元素的items數組中的號碼:

public void Add(T item) { 
    items[length++] = item; 
} 

length++位不是原子;它由一個讀,一個增量和一個寫組成,並且該線程可以在其中任何一個之後被中斷。所以,讓我們改寫這個了一下,看看什麼是真正發生的事情:

public void Add(T item) { 
    int temp = length; 
    length = length + 1; 
    items[temp] = item; 
} 

現在假設兩個線程T1和T2輸入地址在同一時間。下面是一組可能的事件:

T1: int temp = length; 
T2: int temp = length; 
T2: length = length + 1; 
T2: items[temp] = item; 
T1: length = length + 1; 
T1: items[temp] = item; 

的問題存在着相同的值用於temp由兩個線程,因此最後一個線程離開最終覆蓋了第一個放有物品; 最後有一個未指定的項目。

它也沒有幫助,如果length表示要使用的,所以我們可以使用前增量的下一個索引:

public void Add(T item) { 
    items[++length] = item; 
} 

我們再次改寫這個:

public void Add(T item) { 
    length = length + 1; 
    items[length] = item; 
} 

現在,這是一個可能的事件順序:

T1: length = length + 1; 
T2: length = length + 1; 
T2: items[length] = item; 
T1: items[length] = item; 

再次,最後一個線程結束p覆蓋第一個,但是現在未分配的項目是倒數第二個項目。

+2

不僅僅是_this_代碼段,_any_段代碼在「監聽器」上同步。鎖在對象上,而不是代碼。 – paxdiablo

+0

@paxdiablo:是的,我當然忘了提到這一點。編輯... –

+0

+1使其更清晰。 – paxdiablo

0

所以你不會碰到衝突,因爲多個線程通過應用程序競爭作爲明顯的答案。使用Ajax或Swing也要確保正確的偵聽器具有正在偵聽的正確對象。

我使用了一些事件處理程序工具包,他們將管理器抽象爲監聽器,以便他們不必執行愚蠢的操作,比如將所有監聽器放入數組列表中,然後循環查找對象和它的監聽器。

我還沒有完成android,但我確定這個概念是相似的。爲聽衆獲取錯誤的對象是一個問題。

HTH。

3

這是因爲「只有一行代碼」就是這樣。它可能是文件中的一行源代碼,但實現此功能的實際代碼可能是數百條指令,其中任何都可能在任務切換中被中斷。

通過同步(您希望以某種方式在此處或任何地方使用listeners),您保證其他任何執行線程都無法將您的地毯拉出,反之亦然。

1

標準例如:

count++; 

這幕後擴大到

int tmp=count; 
tmp=tmp+1; 
count=tmp; 

(這是因爲處理器不能對存儲器直接操作,並且具有加載變量到寄存器)

這有問題,因爲在加載count和存儲更新的結果之間另一個線程可能已經更新它,這意味着該更新是l導致錯誤的行爲

1

在您提供的示例中,您不僅僅是「同步」一行代碼,而且還鎖定了偵聽器對象,阻止其他線程訪問同時在同一對象上同步的其他線程。

假設你有包含的addListener在類的另一種方法:

public void removeListener(Listener listener) { 
    synchronized (listeners) { 
     listeners.remove(listener); 
    } 
} 

如果線程T已經鎖定了聽衆中的addListener Call對象,然後線S將不得不等待同步塊外,直到線程T釋放偵聽器對象上的鎖。然後,它會獲得鎖,進入同步塊並調用listeners.remove(listener)。

但是,直接訪問偵聽器對象的代碼不會等待獲取鎖。

public void unsafeRemoveListener(Listener listener) { 
    listeners.remove(listener); 
} 
+0

@zjs,請注意你的[建議改進](http://stackoverflow.com/suggested-edits/117567),同時改進這個答案,相當顯着地改變了帖子的含義。 [我們不鼓勵編輯徹底改變問題或答案的含義](http://stackoverflow.com/privileges/edit):_to在不改變it_的情況下澄清帖子的含義。我建議在您的內容中發佈一條新帖子,一旦您得到一些贊成票,您可以在這裏發表評論。 :) – sarnold