多个线程在 java 中使用同一个对象是否复制它?

Do multiple threads Using the same object in java make a copy of it?

Java中的多个线程如何处理单个对象引用[=24] =] 传递给他们?

他们是复制该对象然后使用它,还是使用同一个对象?

欢迎任何技术解释。

如果线程将一些数据传递给对象并进入休眠状态,而另一个线程使用相同的对象向其传递数据并进入休眠状态,我无法理解这一点。

对象中的最后一个数据会覆盖之前的数据吗?

Do they make a copy of the object and then use it or do they use the same one

没有复制完成。线程将使用相同的对象。

Do they make a copy of the object and then use it?

没有。他们都使用同一个对象。

但是,典型现代计算机的内存系统设计意味着对象的某些状态可能保存在与多个 处理器相关联的机器寄存器和内存缓存中 在 multi-processor 系统中。

因此,共享可变对象需要适当的同步,原因有二:

  • 避免两个线程同时尝试读取/修改对象导致的竞争条件,并且
  • 避免一个线程造成的内存危险,而不是看到另一个线程由于缓存而进行的变量更新(见上文)。

Any technical explanation is welcome.

这与在 single-threaded 上下文中传递对象的方式没有什么不同。对对象的引用按值传递。没有对象的复制。仅复制参考文献。

Will the last data overwrite the previous one in the object?

如果你正确地实现了同步,那么是的。如果您不这样做,则无法保证。事实上,一个线程 可能 永远不会看到 对共享对象所做的更新......如果你没有正确实现同步.

事实上,Java 语言规范中有一节定义了 Java 内存模型。这指定了您需要在程序中执行的操作,以确保一个线程所做的内存更改始终对另一个线程可见。

你可以看看java内存模型。您会看到所有对象都存储在堆内存中,该内存在整个应用程序中共享。每个线程共享相同的堆 space,但它们也有自己的堆栈内存,用于存储对对象的引用。所以如果一个线程在对象上工作,它有自己对该对象的引用,但这个引用指向堆 space 中的对象,每个线程都会看到。所以回答你的问题,如果第二个线程将在对象上做一些事情,然后进入睡眠甚至死亡,前一个线程在唤醒时会看到这些变化,因为它的引用指向同一个对象。

我找到了可以帮助您理解的有趣图像:

图片来自:http://tutorials.jenkov.com/java-concurrency/java-memory-model.html

来自个人的例子。我正在为个人成长制作一个 space 入侵者游戏,它使用了多个 Thread。一个 Thread 处理渲染,另一个处理游戏逻辑。这是在我牢牢掌握并发性以及如何在 Java 中正确实现它之前。

无论如何,我有一个 ArrayList<Laser> 可以在游戏的任何给定帧(或者我认为的)保存系统中的所有 Laser。由于 space 入侵者的性质,List 非常活跃。随着新的 Laser 的产生和移除,它不断被添加到地图之外或与实体相撞。

这一切都很好,除了我偶尔会收到 ConcurrentModificationException。我花了很长时间才弄清楚到底发生了什么。事实证明,渲染 Thread 在极少数情况下被发现在迭代 List<Laser> 的同时,游戏逻辑 Thread 正在添加新的 Laser 或删除他们。

这是因为当 Thread 1 获得指向内存中 List 对象点的指针时,它几乎就像是在该内存块上的 "operating" 进程中。 Thread 2 出现并抓住那个 same 指针,却不知道该对象已经在 "operating table" 上,正被 Thread 1 修改,它试图去关于做它想做的事,却发现 Thread 2 认为它真正了解的对象是不正确的,因为 Thread 1 的修改。这就是最终抛出 ConcurrentModificationException.

的原因

这可以通过几种不同的方式解决。我认为现在解决这个问题最有效和安全的方法是使用 Java 8 的 Stream API (如果做得好,它将确保 true 并行),或者你可以使用 synchronized 块(我认为它们是在 Java 5 中出现的)。使用 synchronized 块,当前正在查看该对象的 Thread 将基本上锁定它,甚至不允许任何其他 Thread 观察该对象。 Thread 完成后,它会释放对象供下一个 Thread 对其进行操作。

这里有两个如何使用的例子 synchronized:

public synchronized void xamp1(List<Laser> lasers){
    for(Laser l:lasers){
        //code to observe or modify
    }
}


public void xamp2(List<Laser> lasers){
   synchronized(lasers){
        for(Laser l:lasers){
             //code to observe or modify
        }
   }
}

线程可以复制,如果:

  1. 你把代码写到那个
  2. 传入的对象实际上可以是"copied"(即克隆)

换句话说:默认是没有隐式复制。没有 1 个线程;不涉及 N 个线程。

如果您需要这样的功能,您已经自己实现了。

对于简单的情况,比如传递一个列表;这可以很容易地完成(对于 top 级别):

List<Whatever> duplicatedList = new ArrayList<>(existingList);

然后将duplicatedList交给其他线程。但当然:这只会创建多个列表 - 列表条目仍然引用完全相同的对象!

深度克隆是一个在java很难做到的概念,因此在实践中很少使用。

Do they make a copy of the object and then use it, or do they use the same one?

实际上两者都有。

现代计算机是根据 对称多处理器体系结构 (SMP) 设计的,其中多个 CPU 竞争访问公共内存系统。为了减少竞争,每个处理器都有自己的 高速缓存 -- 少量 higher-speed 内存 -- 它保存属于 main 的数据的工作副本内存。

缓存发生在硬件中,而硬件对 Java 对象一无所知,因此它发生在 byte-by-byte 级别,而不是 object-by-object 级别。

在语言层面,一个对象只有一个副本,但在硬件层面,不同处理器上的不同线程运行可以有自己的副本parts 的对象。


处理器上的不同线程 运行 如何就某些对象的实际外观达成一致的主题称为 缓存协调。

Java 对不同线程何时以及如何协调它们的缓存有严格的规定。要了解有关这些规则的更多信息,Google for "Java" and "memory model", or "memory visibility", or "happens before."


如果您不想阅读所有血淋淋的细节,秘诀在于明智地使用 synchronized 块和 synchronized 方法。如果您只想记住一条规则,请记住这一点:无论一个线程在离开 synchronized 块之前对共享对象执行的任何操作,都保证在这些线程进入 synchronized 块后对其他线程可见。在同一把锁上。