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))(_ + _)
}
正如我在 中提到的,您的示例存在几个问题。
我是 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))(_ + _)
}
正如我在