Java CompletableFuture比Scala Future更简洁,更快捷

时间:2018-01-20 16:33:36

标签: scala future completable-future

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

2 个答案:

答案 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中提到的,您的示例存在一些问题。