在Java的项目中使用scala的ParHashMap而不是ConcurrentHashMap

时间:2013-11-23 16:20:06

标签: java multithreading scala java.util.concurrent

我有一个相当复杂的项目,它大量使用Java的多线程。在我之前的一个问题的答案中,我描述了一个丑陋的黑客,它应该克服固有的无法并行迭代Java的ConcurrentHashMap。虽然它有效但我不喜欢丑陋的黑客,而且我在尝试在真实系统中引入提出的概念证明时遇到了很多麻烦。试图找到替代解决方案我遇到了Scala的ParHashMap,它声称实现了foreach方法,它似乎并行运行。在我开始学习一种新语言来实现单一功能之前,我想问一下:

1)Scala foreach可扩展的ParHashMap方法是什么?

2)从Scala调用Java代码是否简单明了,反之亦然?我只是提醒代码是并发的并使用泛型。

3)将部分代码库切换到Scala会不会有性能损失?

作为参考,这是我之前关于ConcurrentHashMap

的并行迭代的问题

Scalable way to access every element of ConcurrentHashMap<Element, Boolean> exactly once

修改

我已经实现了概念验证,可能非常非惯用的Scala,但它的工作正常。鉴于其标准库和任何可用的第三方库的当前状态,AFAIK在Java中实现相应的解决方案是不可能的。

import scala.collection.parallel.mutable.ParHashMap

class Node(value: Int, id: Int){
    var v = value
    var i = id
    override def toString(): String = v toString
}

object testParHashMap{
    def visit(entry: Tuple2[Int, Node]){
        entry._2.v += 1
    }
    def main(args: Array[String]){
        val hm = new ParHashMap[Int, Node]()
        for (i <- 1 to 10){
            var node = new Node(0, i)
            hm.put(node.i, node)
        }

        println("========== BEFORE ==========")
        hm.foreach{println}

        hm.foreach{visit}

        println("========== AFTER ==========")
        hm.foreach{println}

    }
}

2 个答案:

答案 0 :(得分:1)

我对此提出了一些警告:

  • 虽然我可以做一些事情,但我认为自己对Scala来说相对较新。
  • 我只读过但从未使用par所描述的内容here
  • 我从未试图完成你想要完成的任务。

如果您仍然关心我要说的话,请继续阅读。

首先,这是一个学术paper,描述了并行集合的工作原理。

关于你的问题。

1)当谈到多线程时,Scala使生活比Java更容易。抽象真是棒极了。您从ParHashMap调用获得的par会将工作分配给多个线程。我不能说如果没有更好地理解你的机器,配置和用例,这将如何扩展,但做得正确(特别是关于副作用)它将至少与Java实现一样好。但是,您可能还希望查看Akka以更好地控制所有内容。听起来这可能比仅仅ParHashMap更适合您的用例。

2)使用JavaConvertersasJavaasScala方法在Java和Scala集合之间进行转换通常很简单。我建议虽然确保你的方法的公共API调用“看起来像Java”,因为Java是最不常见的分母。此外,在这种情况下,Scala是一个实现细节,你永远不想泄漏它们。因此,将抽象保持在Java级别。

3)我猜想Scala实际上会有性能提升 - 在运行时。但是,您会发现编译时间慢得多(可以解决这个问题.ash)。 Scala的作者的Stack Overflow post已经陈旧但仍然相关。

希望有所帮助。那是你遇到的一个问题。

答案 1 :(得分:0)

由于Scala编译为与Java相同的字节码,因此无论执行任何任务,都可以在两种语言中执行相同的操作。然而,在Scala中有一些更容易解决的问题,但如果值得学习新语言则是另一个问题。特别是因为Java 8将包含您所要求的内容:在列表上简单并行执行函数。

但即使现在你可以用Java做到这一点,你只需要自己编写Scala已有的东西。

final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
//...
final Entry<String, String>[] elements = (Entry<String, String>[]) myMap.entrySet().toArray();
final AtomicInteger index = new AtomicInteger(elements.length);

for (int i = Runtime.getRuntime().availableProcessors(); i > 0; --i) {
  executor.submit(new Runnable() {

    public void run() {
      int myIndex;
      while ((myIndex = index.decrementAndGet()) >= 0) {
        process(elements[myIndex]);
      }
    }
  });
}

诀窍是将这些元素拉入临时数组,因此线程可以以线程安全的方式取出元素。显然,在这里进行一些缓存而不是每次都重新创建Runnables和数组,因为Runnable创建可能已经花费了比实际任务更长的时间。

也可以将元素复制到(可重用的)LinkedBlockingQueue中,然后让线程轮询/接受它。然而,这会增加更多开销,并且仅对需要至少一些计算时间的任务才合理。

我不知道Scala是如何工作的,但鉴于它需要在同一个JVM上运行,它会在后台执行类似的操作,它恰好可以在标准库中轻松访问。