多个线程在java中使用相同的对象制作它的副本吗?

时间:2017-01-31 11:45:31

标签: java multithreading thread-safety thread-synchronization java-threads

Java 中的多个线程如何处理传递给它们的单个对象引用

他们是否制作了该对象的副本然后使用它,还是使用相同的副本?

欢迎任何技术解释。

我无法在上下文中理解这一点,如果线程将一些数据传递给对象并进入休眠状态,而另一个线程处于睡眠状态,则另一个线程使用相同的对象将数据传递给它并进入休眠状态。

最后一个数据是否会覆盖对象中的前一个数据?

6 个答案:

答案 0 :(得分:4)

  

他们是制作对象的副本然后使用它,还是使用相同的?

两者,实际上。

现代计算机是根据 Symmetric Multiprocessor Architecture(SMP)设计的,其中有几个CPU竞争访问公共内存系统。为了减少竞争,每个处理器都有自己的缓存 - 少量高速内存 - 它可以保存属于主内存的数据副本。

缓存发生在硬件中,硬件对Java对象一无所知,因此它在逐字节级别上发生,而不是在逐个对象级别上发生。

在语言级别,只有一个对象的副本,但在硬件级别,在不同处理器上运行的不同线程可以拥有自己的对象的部分副本。

关于处理器上运行的不同线程如何就某些对象实际应该看起来如何达成一致的主题称为缓存协调。

Java对于不同线程何时以及如何协调其缓存有严格的规定。要了解有关这些规则的更多信息,Google可以使用“Java”和“内存模型”,或“内存可见性”,或“之前发生过”。

如果您不想阅读所有血腥细节,那么秘密就是明智地使用synchronized块和synchronized方法。如果您只想记住一条规则,请记住:无论一个线程在离开synchronized块之前对共享对象做了什么,保证在这些线程进入synchronized块之后对其他线程可见在同一把锁上。

答案 1 :(得分:1)

  

他们是否制作了该对象的副本然后使用它?

没有。它们都使用相同的对象。这就是为什么正确的同步对于共享的可变对象非常重要的原因。

  

欢迎任何技术解释。

这与在单线程上下文中传递对象的方式没有什么不同。对象的引用按值传递。没有复制对象。只复制参考文献。

答案 2 :(得分:0)

  

他们是否制作了该对象的副本,然后使用它或者使用相同的

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

答案 3 :(得分:0)

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

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

http://tutorials.jenkov.com/java-concurrency/java-memory-model.html

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

答案 4 :(得分:0)

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

无论如何,我有一个ArrayList<Laser>在游戏中的任何给定帧中保存系统中的所有Laser(或者我认为)。由于空间入侵者的性质,List非常动态。它不断被添加到新的Laser中,因为它们离开地图或与实体相撞而产生并被移除。

这一切都很好,除了我经常收到ConcurrentModificationException。我花了很长时间才弄明白究竟发生了什么。事实证明,渲染Thread很少会在游戏逻辑List<Laser>添加新Thread或删除时同时被Laser重复捕获它们。

这是因为当Thread 1获得指向内存中List个对象点的指针时,它几乎就像是在该内存块上“操作”的过程中。 Thread 2出现并抓住相同的指针,发现该对象已经在Thread 1修改的“操作表”上,它试图去做它的事情意图,只是因为Thread 1的修改而发现Thread 2认为该对象真正知道的内容不正确。这就是最终抛出ConcurrentModificationException

这可以通过几种不同的方式解决。我认为现在最有效和最安全的解决方法是使用Java 8的Stream API(如果操作正确,它将确保真正的并行性),或者您可以使用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
        }
   }
}

答案 5 :(得分:0)

线程可以复制,如果:

  1. 您将代码写入
  2. 传入的对象实际上可以“复制”(克隆为)
  3. 换句话说:默认情况下,没有隐式复制。没有1个线程;不涉及N个线程。

    如果您需要此类功能,请自行实施。

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

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

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

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