Java CompletableFuture 比 Scala Future 更简洁、更快

Java CompletableFuture is more concise and faster then Scala Future

我是 Java 开发人员,目前正在学习 Scala。人们普遍承认 Java 比 Scala 更冗长。我只需要同时调用 2 个或更多方法,然后组合结果。 docs.scala-lang.org/overviews/core/futures.html 的 Scala 官方文档建议为此使用 for-comprehention。所以我直接使用了那个开箱即用的解决方案。然后我想到我将如何使用 CompletableFuture 来完成它并且惊讶于它产生了更简洁和更快的代码,然后是 Scala 的 Future

让我们考虑一个基本的并发情况:对数组中的值求和。为简单起见,让我们将数组分成两部分(因此它将是 2 个工作线程)。 Java的sumConcurrently只需要4行,而Scala的版本需要12行。另外 Java 的版本在我的电脑上快 15%

完整代码,基准优化。 Java 实现:

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);
    }
}

Scala 的实现:

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 代码的任何解释和建议?

你的 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)
}

这一切都是因为我们必须对列表进行分区。假设我们必须编写一个函数,它接受一些列表和 returns 它们的总和使用多个并发计算,

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 完成)时,这意味着该计算必然会在时间 t1。你可以在 Scala 中像这样阻塞,

val sumFuture = sumConcurrently(List(1, 2, 3, 4))

val sum = Await.result(sumFuture, Duration.Inf)

val anotherSum = sum + 100

println("another sum = " + anotherSum)

但这一切的意义何在,您是 blocking 当前线程,同时要完成这些线程上的计算。为什么不将整个计算移动到未来本身。

val sumFuture = sumConcurrently(List(1, 2, 3, 4))

val anotherSumFuture = sumFuture.map(s => s + 100)

anotherSumFuture.foreach(s => println("another sum = " + s))

现在,您不会在任何地方阻塞,线程可以在任何需要的地方使用。

Scala 中的

Future 实现和 api 旨在使您能够编写尽可能避免阻塞的程序。

对于手头的任务,以下可能也不是最简洁的选项:

def sumConcurrently(numbers: Vector[Int]): Future[Int] = {
  val (v1, v2) = numbers.splitAt(numbers.length / 2)
  Future(v1.sum).zipWith(Future(v2.sum))(_ + _)
}

正如我在 中提到的,您的示例存在几个问题。