我有一个相当复杂的项目,它大量使用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}
}
}
答案 0 :(得分:1)
我对此提出了一些警告:
par
所描述的内容here。 如果您仍然关心我要说的话,请继续阅读。
首先,这是一个学术paper,描述了并行集合的工作原理。
关于你的问题。
1)当谈到多线程时,Scala使生活比Java更容易。抽象真是棒极了。您从ParHashMap
调用获得的par
会将工作分配给多个线程。我不能说如果没有更好地理解你的机器,配置和用例,这将如何扩展,但做得正确(特别是关于副作用)它将至少与Java实现一样好。但是,您可能还希望查看Akka以更好地控制所有内容。听起来这可能比仅仅ParHashMap
更适合您的用例。
2)使用JavaConverters
和asJava
和asScala
方法在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上运行,它会在后台执行类似的操作,它恰好可以在标准库中轻松访问。