2011-10-07 110 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