同步代码块

发布于 2024-12-08 21:31:47 字数 207 浏览 0 评论 0原文

为什么人们只需要 1 行代码就可以“同步”?有什么可以“同步”的?

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

编辑:谢谢大家。大家的回答都很好!

Why do people "synchronize" for just 1 line of code? What is there to "synchronize"?

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

EDIT: Thank you everyone. Very good answers to from all!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

风吹雨成花 2024-12-15 21:31:47

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

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

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

编辑:为了举例说明事情可能会如何变得混乱,假设这个 add 的简化实现,其中 lengthitems 中的元素数量code> 数组:

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 表示要使用的下一个索引,那么我们可以使用预增量:

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;

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

synchronized on its own means that if multiple threads try to run this piece of code at the same time, only one of those threads is allowed inside the block at any given time. synchronized (listeners) uses listeners as a lock identifier, which means that this restriction applies to all blocks which synchronize on that variable - if one thread is inside one of those blocks, no other thread may enter any of them.

Even though there's only a single function call in a block, this can still make sense: that function consists of a lot of other instructions, and control may switch to a different thread while the first one is in the middle of that function. If the function is not thread-safe, that can cause problems, such as data getting overwritten.

In this particular case, the function call consists of adding a value to a collection listeners. While it's not impossible to make a thread-safe collection, most collections are not thread-safe for multiple writers. Thus, in order to ensure the collection does not get messed up, synchronized is needed.

EDIT: To give an example of how things may get messed up, assume this simplified implementation of add, where length is the number of elements in the items array:

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

That length++ bit is not atomic; it consists of a read, an increment, and a write, and the thread can get interrupted after any of them. So, let's rewrite this a bit, to see what's really happening:

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

Now assume two threads T1 and T2 enter Add at the same time. Here's one possible set of events:

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

The problem there is that the same value is used for temp by both threads, so the last thread to leave ends up overwriting the item that the first one put there; and there's an unassigned item at the very end.

It also doesn't help if length represented the next index to be used so we can use a preincrement:

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

Again, we rewrite this:

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

Now this is a possible sequence of events:

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

Once again, the last thread ends up overwriting the first, but now the unassigned item is the second-to-last item.

梦回梦里 2024-12-15 21:31:47

这是因为“只需 1 行代码”根本不是这样的。它可能是文件中的一行源代码,但在幕后运行的实际代码很可能是数百条指令,其中任何都可能在任务切换中中断。

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

It's because that "just 1 line of code" is nothing of the sort. It may be one line of source code in your file but the actual code running behind the scenes to achieve this may well be hundreds of instructions, any of which could be interrupted in a task switch.

By synchronising (here and anywhere else you wish to use listeners in some way), you guarantee that no other thread of execution will be able to pull the rug out from under you, or vice versa.

空袭的梦i 2024-12-15 21:31:47

标准示例:

count++;

这在幕后扩展为

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

(这是因为处理器无法直接在内存上操作并且必须将变量加载到寄存器中)

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

standard example:

count++;

this is expanded behind the scenes to

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

(this is because processors cannot operate directly on memory and has to load the variables into registers)

this has issues because between loading count and storing the updated result another thread could have updated it, this means that that update is lost leading to erroneous behavior

野生奥特曼 2024-12-15 21:31:47

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

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

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

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

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

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

In the example you provided, you are not only "synchronizing" one line of code but you are also locking the listeners object, preventing it from being accessed by other threads which also synchronize on the same object.

Assume you had another method on the class that contained addListener:

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

If thread T has locked the listeners object in a call to addListener, then thread S would have to wait on outside the synchronized block until thread T releases the lock on the listeners object. Then, it would acquire the lock, enter the synchronized block, and call listeners.remove(listener).

However, code which directly accessed the listeners object would not wait to acquire the lock.

public void unsafeRemoveListener(Listener listener) {
   listeners.remove(listener);
}
初吻给了烟 2024-12-15 21:31:47

因此,您不会遇到冲突,因为多个线程在应用程序中竞争,这是显而易见的答案。对于 Ajax 或 Swing,您也希望确保正确的侦听器拥有其正在侦听的正确对象。

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

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

HTH。

So you don't run into conflicts as multiple threads race through an app as the obvious answer. With Ajax or Swing too you want to ensure the correct listener has the correct object it's listening for.

With some of the event handler toolkits I've worked with they abstract listeners off to managers so they don't have to do silly things like put all the listeners in an arrayList and then loop through to find the right match between the object and the listener for it.

I haven't done android yet, but I'm sure the concept is similar. Getting the wrong object for the listener is a problem.

HTH.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文