我是Java开发人员,目前正在学习Scala。人们普遍承认,Java比Scala更冗长。我只需要同时调用两个或更多方法然后合并结果。 docs.scala-lang.org/overviews/core/futures.html上的官方Scala文档建议使用for-comprehention
。所以我直截了当地使用了开箱即用的解决方案。然后我想我会如何用CompletableFuture
做到这一点并且惊讶于它产生了更简洁和更快的代码,然后Scala的Future
让我们考虑一个基本的并发案例:汇总数组中的值。为简单起见,让我们将数组拆分为2个部分(因此它将是2个工作线程)。 Java的sumConcurrently
只需要 4 LOC,而Scala的版本需要 12 LOC。此外,Java的版本在我的计算机上 15%更快。
完整代码,不基准优化。 Java impl。:
public class CombiningCompletableFuture {
static int sumConcurrently(List<Integer> numbers) throws ExecutionException, InterruptedException {
int mid = numbers.size() / 2;
return CompletableFuture.supplyAsync( () -> sumSequentially(numbers.subList(0, mid)))
.thenCombine(CompletableFuture.supplyAsync( () -> sumSequentially(numbers.subList(mid, numbers.size())))
, (left, right) -> left + right).get();
}
static int sumSequentially(List<Integer> numbers) {
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
} catch (InterruptedException ignored) { }
return numbers.stream().mapToInt(Integer::intValue).sum();
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<Integer> from1toTen = IntStream.rangeClosed(1, 10).boxed().collect(toList());
long start = System.currentTimeMillis();
long sum = sumConcurrently(from1toTen);
long duration = System.currentTimeMillis() - start;
System.out.printf("sum is %d in %d ms.", sum, duration);
}
}
斯卡拉的意见:
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
object CombiningFutures extends App {
def sumConcurrently(numbers: Seq[Int]) = {
val splitted = numbers.splitAt(5)
val leftFuture = Future {
sumSequentally(splitted._1)
}
val rightFuture = Future {
sumSequentally(splitted._2)
}
val totalFuture = for {
left <- leftFuture
right <- rightFuture
} yield left + right
Await.result(totalFuture, Duration.Inf)
}
def sumSequentally(numbers: Seq[Int]) = {
Thread.sleep(1000)
numbers.sum
}
val from1toTen = 1 to 10
val start = System.currentTimeMillis
val sum = sumConcurrently(from1toTen)
val duration = System.currentTimeMillis - start
println(s"sum is $sum in $duration ms.")
}
如何在不影响可读性的情况下改进Scala代码的任何解释和建议?
答案 0 :(得分:3)
sumConcurrently的详细scala版本,
def sumConcurrently(numbers: List[Int]): Future[Int] = {
val (v1, v2) = numbers.splitAt(numbers.length / 2)
for {
sum1 <- Future(v1.sum)
sum2 <- Future(v2.sum)
} yield sum1 + sum2
}
更简洁的版本
def sumConcurrently2(numbers: List[Int]): Future[Int] = numbers.splitAt(numbers.length / 2) match {
case (l1, l2) => Future.sequence(List(Future(l1.sum), Future(l2.sum))).map(_.sum)
}
所有这一切都是因为我们必须对列表进行分区。让我们说我们必须编写一个函数,它接受一些列表,并使用多个并发计算返回它们之和的总和,
def sumConcurrently3(lists: List[Int]*): Future[Int] =
Future.sequence(lists.map(l => Future(l.sum))).map(_.sum)
如果上面看起来很神秘......那就让我去除它,
def sumConcurrently3(lists: List[Int]*): Future[Int] = {
val listOfFuturesOfSums = lists.map { l => Future(l.sum) }
val futureOfListOfSums = Future.sequence(listOfFuturesOfSums)
futureOfListOfSums.map { l => l.sum }
}
现在,无论何时在计算中使用Future
的结果(假设未来在时间t1
完成),这意味着此计算必然会在时间{{1}之后发生}。你可以在Scala中使用这样的阻塞来实现它,
t1
但重要的是,当你完成那些线程的计算时,你是val sumFuture = sumConcurrently(List(1, 2, 3, 4))
val sum = Await.result(sumFuture, Duration.Inf)
val anotherSum = sum + 100
println("another sum = " + anotherSum)
当前线程。为什么不将整个计算移到未来本身。
blocking
现在,您无法在任何地方阻止,并且可以在任何需要的地方使用线程。
Scala中的 val sumFuture = sumConcurrently(List(1, 2, 3, 4))
val anotherSumFuture = sumFuture.map(s => s + 100)
anotherSumFuture.foreach(s => println("another sum = " + s))
实现和api旨在使您能够编写程序,尽可能避免阻塞。
答案 1 :(得分:1)
对于手头的任务,以下可能不是最简洁的选择:
def sumConcurrently(numbers: Vector[Int]): Future[Int] = {
val (v1, v2) = numbers.splitAt(numbers.length / 2)
Future(v1.sum).zipWith(Future(v2.sum))(_ + _)
}
正如我在comment中提到的,您的示例存在一些问题。