Java对象分配开销

时间:2008-09-03 19:37:59

标签: java xml dom concurrency

我在Java中编写一个不可变的DOM树,以简化多线程的访问。*

但是,它确实需要尽快支持插入和更新。由于它是不可变的,如果我对树的第N级节点进行更改,我需要分配至少N个新节点才能返回新树。

我的问题是,每次修改树时预先分配节点而不是创建新节点会快得多吗?这样做很容易 - 保留一个数百个未使用节点的池,并从池中拉出一个,而不是在修改操作需要时创建一个。当没有其他事情发生时,我可以补充节点池。 (如果不是很明显,在这个应用程序中执行时间比堆空间要高得多)

这样做是否值得?关于加速它的任何其他提示?

或者,有人知道一个不可变的DOM库吗?我搜索过,但找不到任何东西。

*注意:对于那些不熟悉不变性概念的人来说,它基本上意味着在对更改它的对象的任何操作中,该方法返回对象的副本,并且更改到位,而不是比改变的对象。因此,如果另一个线程仍在读取对象,它将继续愉快地操作“旧”版本,不知道已经进行了更改,而不是崩溃可怕。见http://www.javapractices.com/topic/TopicAction.do?Id=29

6 个答案:

答案 0 :(得分:12)

现在,对象创建非常快,对象池的概念已经过时了(至少在一般情况下;连接池当然仍然有效)。

避免过早优化。在您复制时需要时创建节点,然后查看它是否变得非常慢。如果是这样,那么请研究一些加速它的技术。但是,除非你已经知道你所拥有的东西还不够快,否则我不会介绍你需要的所有复杂性来进行合并。

答案 1 :(得分:3)

我讨厌给出一个不答复,但我认为回答像这样的性能问题的唯一明确方法可能是你编写两种方法,对两者进行基准测试,并比较结果。

答案 2 :(得分:1)

  

我不确定你是否可以避免显式同步某些方法,以确保一切都是线程安全的。

您需要同步一个或另一个使新创建的节点可用于其他线程的一个特定情况,否则您可能会冒险VM / CPU重新排序在写入共享引用之后的字段写入节点,暴露派对构造对象。

  

尝试更高层次的思考。你有一个IMMUTABLE树(基本上是一组指向其子节点的节点)。您想在其中插入节点。然后,没有出路:你必须创建一个新的整个树。

如果选择将树实现为指向子节点的一组节点,则必须沿着已更改节点的路径创建新节点到根节点。其他的具有与以前相同的值,并且通常是共享的。因此,您需要创建一个部分新树,这通常意味着(已编辑节点的深度)父节点。

如果您可以应对不那么直接的实现,那么您应该能够使用类似于Purely Functional Data Structures中描述的技术来创建节点的一部分,以降低创建的平均成本,或者您可以使用半功能方法绕过它(例如创建一个包装现有迭代器的迭代器,但返回新节点而不是旧节点,以及随着时间的推移在结构中修复此类补丁的机制)。在这种情况下,XPath样式api可能比DOM api更好 - 它可能会使节点与树分离得更多,并且更智能地处理变异树。

答案 3 :(得分:0)

我对你最初想要做的事感到有些困惑。您希望所有节点都是不可变的并且您想要将它们集中在一起?这两个想法不是互相排斥的吗?当你从池中拉出一个对象时,你不是必须调用一个setter来链接这些孩子吗?

我认为使用不可变节点可能不会为您提供首先需要的线程安全性。如果1个线程在节点上进行迭代(搜索或其他),而另一个线程正在添加/删除节点,会发生什么?搜索结果不会无效吗?我不确定你是否可以避免显式同步某些方法,以确保一切都是线程安全的。

答案 4 :(得分:0)

@Outlaw Programmer

  

将物体拉出物体时   游泳池,你不必调用一个   塞特把孩子们联系起来?

每个节点不需要在包内部是不可变的,只能是向外的接口。 node.addChild()将是一个具有公共可见性的不可变函数并返回一个Document,而node.addChildInternal()将是一个具有包可见性的普通,可变函数。但由于它是包的内部,因此它只能被称为addChild()的后代,并且整个结构被保证是线程安全的(前提是我同步访问对象池)。你看到这个缺陷了吗?如果是的话,请告诉我!

  

我认为使用不可变节点可能不会为您提供首先需要的线程安全性。如果1个线程在节点上进行迭代(搜索或其他),而另一个线程正在添加/删除节点,会发生什么?

树作为一个整体将是不可变的。假设我有Thread1和Thread2,以及树dom1。 Thread1在dom1上启动读操作,同时,Thread2在dom1上启动写操作。但是,Thread2所做的所有更改实际上都是对新对象进行的,dom2和dom1将是不可变的。确实,Thread1读取的值将(几微秒)过时,但它不会在IndexOutOfBounds或NullPointer异常上崩溃,或者类似于它正在读取正在写入的可变对象的情况。然后,Thread2可以将包含dom2的事件触发到Thread1,以便它可以再次执行读取并在必要时更新其结果。

编辑:澄清

答案 5 :(得分:0)

我认为@Outlaw有一点意见。 DOM树的结构驻留在节点本身中,具有指向其子节点的节点。要修改树的结构,你必须修改节点,所以你不能将它合并,你必须创建一个新的。

尝试更高层次的思考。你有一个IMMUTABLE树(基本上是一组指向其子节点的节点)。您想在其中插入节点。然后,没有出路:你必须创建一个新的WHOLE树。

是的,不可变树是线程安全的,但它会影响性能。对象创建可能很快,但不会比没有对象创建更快。 :)