繁体   English   中英

同步代码块

[英]Synchronized code block

为什么人们只为一行代码“同步”? 什么是“同步”?

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

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

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

即使块中只有一个函数调用,这仍然有意义:该函数由许多其他指令组成,并且控件可以切换到不同的线程,而第一个在该函数的中间。 如果该函数不是线程安全的 ,则可能导致问题,例如数据被覆盖。

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

编辑:为了举例说明事情可能搞砸了,假设这个简化的add实现,其中lengthitems数组中元素的数量:

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;

问题是两个线程都使用相同的值作为temp ,因此最后一个离开的线程最终会覆盖第一个放在那里的项目; 并且在最后有一个未分配的项目。

如果length表示要使用的下一个索引,那么它也没有帮助,所以我们可以使用preincrement:

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;

再次,最后一个线程最终覆盖第一个,但现在未分配的项目是倒数第二个项目。

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

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

标准示例:

count++;

这是在幕后扩展到的

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

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

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

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

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

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

如果线程T在对addListener的调用中锁定了侦听器对象,则线程S必须在synchronized块外部等待,直到线程T释放对侦听器对象的锁定。 然后,它将获取锁,进入synchronized块,并调用listeners.remove(listener)。

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

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

所以你不会遇到冲突,因为多个线程通过应用程序竞争作为明显的答案。 使用Ajax或Swing,您也希望确保正确的侦听器具有正在侦听的正确对象。

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

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

HTH。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM