Java 同步代码块

Java 同步代码块,java,android,multithreading,thread-safety,Java,Android,Multithreading,Thread Safety,为什么人们只需要一行代码就可以“同步”?什么是“同步” 编辑:谢谢大家。非常好的答案来自所有人 因此,当多个线程在一个应用程序中竞相运行时,您不会遇到冲突,这是显而易见的答案。使用Ajax或Swing时,您希望确保正确的侦听器具有正确的侦听对象 使用我使用过的一些事件处理程序工具包,它们将侦听器抽象到管理器中,这样他们就不必做一些愚蠢的事情,比如将所有侦听器放在arrayList中,然后循环查找对象和侦听器之间的正确匹配 我还没有做过android,但我相信这个概念是相似的。为侦听器获取错误的对

为什么人们只需要一行代码就可以“同步”?什么是“同步”


编辑:谢谢大家。非常好的答案来自所有人

因此,当多个线程在一个应用程序中竞相运行时,您不会遇到冲突,这是显而易见的答案。使用Ajax或Swing时,您希望确保正确的侦听器具有正确的侦听对象

使用我使用过的一些事件处理程序工具包,它们将侦听器抽象到管理器中,这样他们就不必做一些愚蠢的事情,比如将所有侦听器放在arrayList中,然后循环查找对象和侦听器之间的正确匹配

我还没有做过android,但我相信这个概念是相似的。为侦听器获取错误的对象是一个问题

HTH.

这是因为“只有一行代码”不是这样的。它可能是您文件中的一行源代码,但在幕后运行以实现此目的的实际代码可能是数百条指令,其中任何指令都可能在任务切换中中断


通过同步(在这里和任何其他您希望以某种方式使用
侦听器的地方),您可以保证没有其他执行线程能够从您下面拉出地毯,反之亦然。

synchronized
本身意味着如果多个线程试图同时运行这段代码,在任何给定的时间,块中只允许有一个线程
synchronized(listeners)
使用
listeners
作为锁标识符,这意味着此限制适用于在该变量上同步的所有块-如果一个线程位于其中一个块内,则其他线程不能输入任何块

尽管块中只有一个函数调用,但这仍然是有意义的:该函数由许多其他指令组成,而控制可以切换到另一个线程,而第一个线程位于该函数的中间。如果函数不是线程安全的,则可能会导致问题,例如数据被覆盖

在这种特殊情况下,函数调用包括向集合
侦听器添加值。虽然创建线程安全的集合并非不可能,但对于多个编写器来说,大多数集合都不是线程安全的。因此,为了确保收集不会混乱,需要进行
同步

编辑:为了举例说明事情是如何变得一团糟的,假设
add
的简化实现,其中
length
数组中的元素数:

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同时进入Add。以下是一组可能的事件:

T1: int temp = length;
T2: int temp = length;
T2: length = length + 1;
T2: items[temp] = item;
T1: length = length + 1;
T1: items[temp] = item;
T1: length = length + 1;
T2: length = length + 1;
T2: items[length] = item;
T1: items[length] = item;
问题是两个线程对
temp
使用相同的值,因此最后一个线程将覆盖第一个线程放在那里的项目;最后还有一个未分配的项目

如果
length
表示要使用的下一个索引,那么我们可以使用预增量,这也没有帮助:

public void Add(T item) {
  items[++length] = item;
}
再次,我们重写以下内容:

public void Add(T item) {
  length = length + 1;
  items[length] = item;
}
下面是一系列可能的事件:

T1: int temp = length;
T2: int temp = length;
T2: length = length + 1;
T2: items[temp] = item;
T1: length = length + 1;
T1: items[temp] = item;
T1: length = length + 1;
T2: length = length + 1;
T2: items[length] = item;
T1: items[length] = item;
最后一个线程再次覆盖第一个线程,但现在未分配的项目是倒数第二个项目。

标准示例:

count++;
这在幕后扩展到

int tmp=count;
tmp=tmp+1;
count=tmp;
(这是因为处理器不能直接在内存上运行,必须将变量加载到寄存器中)


这有问题,因为在加载
计数
和存储更新结果之间,另一个线程可能已经更新了它,这意味着更新丢失,导致错误行为

在您提供的示例中,您不仅“同步”了一行代码,而且还锁定了侦听器对象,防止也在同一对象上同步的其他线程访问它

假设在包含addListener的类上有另一个方法:

public void removeListener(Listener listener) {
   synchronized (listeners) {
       listeners.remove(listener);
   }
}
如果线程T在调用addListener时锁定了listeners对象,那么线程S必须在同步块外等待,直到线程T释放listeners对象上的锁。然后,它将获取锁,输入同步块,并调用listeners.remove(listener)

但是,直接访问侦听器对象的代码不会等待获取锁

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

不仅仅是这段代码,任何在
侦听器上同步的代码。锁在对象上,而不是代码上。如果没有“已同步”,怎么可能“集合不会出错”?你能进一步解释一下吗?我就快到了,但还没有完全实现。谢谢。@musselwhizzle:我已经更新了一个简单的示例,说明了如果没有同步,事情会变得一团糟。好的。很酷。我现在明白了。我想还有最后一个问题。我如何知道什么时候应该使某些内容同步?我应该考虑什么?@ MySelHeZZLE:作为一个经验法则,任何非线程安全的代码,可以由多个线程调用,是一个候选代码<同步> <代码>(使用共享变量,如果它也影响其他代码)。你是否真的应该同步,这部分是一个判断调用;有时,您不希望一段代码知道线程(例如,此集合示例),因为
synchronized
非常昂贵。你会希望尽可能晚地进行同步,但不要太晚以至于它总是发生。为什么(这)“一行代码”比42行代码更原子化?这就是原因;-)@zjs,请注意,虽然你的回答有所改进,但这篇文章的意义却发生了相当大的变化澄清一篇文章的意思而不改变它。我建议写一篇新文章