前言.......

随着CPU的核数的增加,异步编程模型在并发领域中的得到了越来越多的应用,由于Scala是一门函数式语言,天然的支持异步编程模型,今天主要来看一下Java和Scala中的Futrue,带你走入异步编程的大门。

Future

很多同学可能会有疑问,Futrue跟异步编程有什么关系?从Future的表面意思是未来,一个Future对象可以看出一个将来得到的结果,这就和异步执行的概念很像,你只管自己去执行,只要将最终的结果传达给我就行,线程不必一直暂停等待结果,可以在具体异步任务执行的时候去执行其他操作,举个例子:

我们现在在执行做饭这么一个任务,它需要煮饭,烧菜,摆置餐具等操作,如果我们通过异步这种概念去执行这个任务,比如煮饭可能需要比较久的时间,但煮饭这个过程又不需要我们管理,我们可以利用这段时间去烧菜,烧菜过程中也可能有空闲时间,我们可以去摆置餐具,当电饭锅通知我们饭烧好了,菜也烧好了,最后我们就可以开始吃饭了,所以说,上面的“煮饭 -> 饭”,“烧菜 -> 菜”都可以看成一个Future的过程。

Java中的Future

在Java的早期版本中,我们不能得到线程的执行结果,不管是继承Thread类还是实现Runnable接口,都无法获取线程的执行结果,所以我们只能在线程执行的run方法里去做相应的一些业务逻辑操作,但随着Java5的发布,它为了我们带来了Callable和Future接口,我们可以利用这两个接口的特性来获取线程的执行结果。

Callable接口

通俗的讲,Callable接口也是一个线程执行类接口,那么它跟Runnable接口有什么区别呢?我们先来看看它们两个的定义:

1.Callable接口:

@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}

2.Runnable接口:

@FunctionalInterface
public interface Runnable {
public abstract void run();
}

从上面的定义,我们可以看出,两者最大的区别就是对应的执行方法是否有返回值。Callable接口中call方法具有返回值,这便是为什么我们可以通过Callable接口来得到一个线程执行的返回值或者是异常信息。

Future接口

上面说到既然Callable接口能返回线程执行的结果,那么为什么还需要Future接口呢?因为Callable接口执行的结果只是一个将来的结果值,我们若是需要得到具体的结果就必须利用Future接口,另外Callable接口需要委托ExecutorService的submit提交任务去执行,我们来看看它是如何定义的:

 <T> Future<T> submit(Callable<T> task);

 public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}

从submit的方法定义也可以看出它的返回值是一个Future接口类型的值,这里其实是RunnableFuture接口,这是一个很重要的接口,我们来看一下它的定义:

public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}

这个接口分别继承了Runnable和Future接口,而FutureTask又实现了RunnableFuture接口,它们之间的关系:

RunnableFuture有以下两个特点:

  • 继承Runnable接口,还是以run方法作为线程执行入口,其实上面submit方法的具体实现也可以看出,一个Callable的Task再执行的时候会被包装成RunnableFuture,然后以FutureTask作为实现类,执行FutureTask时,还是执行其的run方法,只不过run方法里面的业务逻辑是由我们定义的call方法的内容,当然再执行run方法时,程序会自动将call方法的执行结果帮我们包装起来,对外部表现成一个Future对象。

  • 继承Future接口,通过实现Future接口中的方法更新或者获取线程的的执行状态,比如其中的cancel(),isDone(),get()等方法。

Future程序示例与结果获取

下面是一个简单的Future示例,我们先来看一下代码:

ExecutorService es = Executors.newSingleThreadExecutor();
Future f = es.submit(() -> {
System.out.println("execute call");
Thread.sleep(1000);
return 5;
});
try {
System.out.println(f.isDone()); //检测任务是否完成
System.out.println(f.get(2000, TimeUnit.MILLISECONDS));
System.out.println(f.isDone()); //检测任务是否完成
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}

上面的代码使用了lambda表达式,有兴趣的同学可以自己去了解下,这里我们首先构建了一个ExecutorService,然后利用submit提交执行Callable接口的任务。

为什么是Callable接口呢? 其实这里我们并没有显示声明Callable接口,这里lambda会帮我们自动进行类型推导,首先submit接受Callable接口或Runnble接口类型作为参数,而这里我们又给定了返回值,所以lambda能自动帮我们推导出内部是一个Callable接口参数。

到这里我们应该大致清楚了在Java中的得到Future,那么我们又是如何从Future中得到我们想要的值呢?这个结论其实很容易得出,你只需要去跑一下上面的程序即可,在利用get去获取Future中的值时,线程会一直阻塞,直到返回值或者超时,所以Future中的get方法是阻塞,所以虽然利用Future似乎是异步执行任务,但是在某些需求上还是会阻塞的,并不是真正的异步,stackoverflow上有两个讨论说明了这个问题Future.getwithout blocking when task complete,有兴趣的同学可以去看看。

Scala中的Future

Scala中的Future相对于Java的Future有什么不同呢?我总结了一下几点:

1.创建Future变得很容易

异步编程作为函数式语言的一大优势,Scala对于Future的支持也是非常棒的,首先它也提供了Futrue接口,但不同的是我们在构建Future对象是不用像Java一样那么繁琐,并且非常简单,举个例子:

import scala.concurrent._
import ExecutionContext.Implicits.global val f: Future[String] = Future { "Hello World!" }

是不是非常简单,也大大降低了我们使用Future的难度。

2.提供真正异步的Future

前面我们也说到,Java中的Future并不是全异步的,当你需要Future里的值的时候,你只能用get去获取它,亦或者不断访问Future的状态,若完成再去取值,但其意义上便不是真正的异步了,它在获取值的时候是一个阻塞的操作,当然也就无法执行其他的操作,直到结果返回。

但在Scala中,我们无需担心,虽然它也提供了类似Java中获取值的方式,比如:

| Future | Java | Scala | | ------------- |:-------------:| -----:| | 判断任务是否完成 | isDone | isCompleted | | 获取值 | get | value |

但是我们并不推荐这么做,因为这么做又回到了Java的老路上了,在Scala中我们可以利用Callback来获取它的结果:

val fut = Future {
Thread.sleep(1000)
1 + 1
} fut onComplete {
case Success(r) => println(s"the result is ${r}")
case _ => println("some Exception")
} println("I am working")
Thread.sleep(2000)

这是一个简单的例子,Future在执行完任务后会进行回调,这里使用了onComplete,也可以注册多个回调函数,但不推荐那么做,因为你不能保证这些回调函数的执行顺序,其他的一些回调函数基本都是基于onComplete的,有兴趣的同学可以去阅读一下Future的源码。

我们先来看一下它的运行结果:

I am working
the result is 2

从结果中我们可以分析得出,我们在利用Callback方式来获取Future结果的时候并不会阻塞,而只是当Future完成后会自动调用onComplete,我们只需要根据它的结果再做处理即可,而其他互不依赖的操作可以继续执行不会阻塞。

3.强大的Future组合

前面我们讲的较多的都是单个Future的情况,但是在真正实际应用时往往会遇到多个Future的情况,那么在Scala中是如何处理这种情况的呢?

Scala中的有多种方式来组合Future,那我们就来看看这些方式吧。

1.flatMap

我们可以利用flatMap来组合多个Future,不多说,先上代码:

val fut1 = Future {
println("enter task1")
Thread.sleep(2000)
1 + 1
} val fut2 = Future {
println("enter task2")
Thread.sleep(1000)
2 + 2
} fut1.flatMap { v1 =>
fut2.map { v2 =>
println(s"the result is ${v1 + v2}")
}
}
Thread.sleep(2500)

利用flatMap确实能组合Future,但代码的阅读性实在是有点差,你能想象5个甚至10个map层层套着么,所以我们并不推荐这么做,但是我们需要了解这种方式,其他简洁的方式可能最终转化成的版本也许就是这样的。

2.for yield表达式

我们只是把上面关于flatMap的代码替换一下,看下面:

for {
v1 <- fut1
v2 <- fut2
} yield println(s"the result is ${v1 + v2}")

看上去是不是比之前的方式简洁多了,这也是我们在面对Future组合时推荐的方式,当然不得不说for yield表达式是一种语法糖,它最终还是会被翻译成我们常见的方法,比如flatMap,map,filter等,感兴趣的可以参考它的官方文档。for yield表达式

3.scala-async

另外我们可以用scala-async来组装Futrue语句块,示例如下:

import scala.async.Async.{async, await}

val v1 = async {
await(fut1) + await(fut2)
} v1 foreach {
case r => println(s"the result is ${v1}")
}

这种方式与for yield表达式有啥区别呢?其实主要有两点: - 表达语意更加清晰,不需要用为中间值命名 - 不需要<-等表达式,可减少一定的代码量

scala-async相关的具体信息可以参考它的项目主页。scala-async

总的来说Scala中的Future确实强大,在实现真正异步的情况下,为我们提供许多方便而又简洁的操作模式,其实比如还有Future.reduce(),Future.traverse(),Future.sequence()等方法,这些方法的具体功能和具体使用这里就不讲了,但相关的示例代码都会在我的示例工程里,有兴趣的同学可以去跑跑加深理解。源码链接

总结

这篇文章主要讲解了JVM生态上两大语言Java和Scala在异步编程上的一些表现,这里主要是Future机制,在清楚明白它的概念后,我们才能写出更好的程序,虽然本篇文章没有涉及到Akka相关的内容,但是Akka本身是用Scala写的,而且大量使用了Scala中的Future,相信通过对Future的学习,对Akka的理解会有一定的帮助。

 
 
 

Akka系列(五):Java和Scala中的Future的更多相关文章

  1. SQL Server 2008空间数据应用系列五:数据表中使用空间数据类型

    原文:SQL Server 2008空间数据应用系列五:数据表中使用空间数据类型 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2008 R2调测 ...

  2. Programming In Scala笔记-第五章、Scala中的变量类型和操作

    这一章的一些基础性的东西,主要包括Scala中的基本变量类型,以及相关的一些操作符. 一.简单类型 下表中列出Scala语言中的基本类型,以及其字节长度,其中Byte, Short, Int, Lon ...

  3. Java访问Scala中的Int类型

    出错代码 写java 和 scala 混合代码的时候遇到一个小问题 def extractRefInputFieldsWithType(exprs: JList[RexNode]): Array[(I ...

  4. jvm系列五-java内存模型初览(1)

    本文转载自:再有人问你Java内存模型是什么,就把这篇文章发给他. 网上有很多关于Java内存模型的文章,在<深入理解Java虚拟机>和<Java并发编程的艺术>等书中也都有关 ...

  5. jvm系列(五):Java GC 分析

    Java GC就是JVM记录仪,书画了JVM各个分区的表演. 什么是 Java GC Java GC(Garbage Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之 ...

  6. jvm系列五-java内存模型(2)

    原作者系列文章链接:并发编程系列博客传送门 前言# 在网上看了很多文章,也看了好几本书中关于JMM的介绍,我发现JMM确实是Java中比较难以理解的概念.网上很多文章中关于JMM的介绍要么是照搬了一些 ...

  7. 我的成长比价系列:java web开发过程中遇到的错误一:sql语句换行错误

    字符串换行导致的错误,确切的说是马虎的错误,自己在编写简单的servlet项目时,在StudentDao.java 中的  查询语句:String  sql= "SELECT Type,fl ...

  8. Elasticsearch系列(五)----JAVA客户端之TransportClient操作详解

    Elasticsearch JAVA操作有三种客户端: 1.TransportClient 2.JestClient 3.RestClient 还有种是2.3中有的NodeClient,在5.5.1中 ...

  9. 【Kubernetes 系列五】在 AWS 中使用 Kubernetes:EKS

    目录 1. 概述 2. 版本 3. 预备 3.1. 操作环境 3.2. 角色权限 3.2.1. CloudFormation 完全权限 3.2.2. EKS 读写权限 3.2.3. EC2 相关权限 ...

随机推荐

  1. 红帽Linux故障定位技术详解与实例(3)

    红帽Linux故障定位技术详解与实例(3)   在线故障定位就是在故障发生时, 故障所处的操作系统环境仍然可以访问,故障处理人员可通过console, ssh等方式登录到操作系统上,在shell上执行 ...

  2. Centos 7误删Yum,如何补救???

    1. 查看centos 的版本号 cat /etc/redhat-release 2. 进入阿里云源站地址:http://mirrors.aliyun.com/  找到对应的版本号 3.下载相应的yu ...

  3. 2019ICPC沈阳网络赛-D-Fish eating fruit(树上DP, 换根, 点分治)

    链接: https://nanti.jisuanke.com/t/41403 题意: State Z is a underwater kingdom of the Atlantic Ocean. Th ...

  4. 性能优化(1+N,list与iterator,缓存,事务)

    1.注意session.clear()的运用,尤其是不断分页循环的时候 A 在一个大集合中进行遍历,取出其中含有敏感字的对象 B 另一种形式的内存泄露. 2.1+N问题 问题描述:如@ManyToOn ...

  5. 使用jmeter+ant+jenkins实现接口自动化测试

    摘自:https://blog.51cto.com/6183574/2318519 本次使用linux环境搭建自动化框架 一.配置ANT 首先下载Ant压缩包,下载地址:http://ant.apac ...

  6. CSS 的伪元素是什么?

    CSS伪元素是用来添加一些选择器的特殊效果.用于:向某个选择器中的文字的首行. ㈠语法 ①伪元素的语法: selector:pseudo-element {property:value;} ②CSS类 ...

  7. jquery checkbox选择器 语法

    jquery checkbox选择器 语法 作用::checkbox 选择器选取类型为 checkbox 的 <input> 元素.大理石平台价格表 语法:$(":checkbo ...

  8. Nowcoder Typing practice ( Trie 图 )

    题目链接 题意 : 给出 n 个串.然后给出一个问询串.问你对于问询串的每一个前缀.需要至少补充多少单词才能使得其后缀包含 n 个串中的其中一个.注意 '-' 字符代表退格 分析 : 多串的匹配问询自 ...

  9. ASE高级软件工程 第一周博客作业

    1.自我介绍 我叫姚顺,是来自哈尔滨工业大学计算机学院的一名大四本科生,专业方向计算机科学,目前在KC组实习.平时的业余时间主要用来打篮球,听音乐,跑步,当然还有游戏(划掉).之前的大学三年主要用来做 ...

  10. TTTTTTTTTTTTTT CF 645D 点的优先级

    题意:给你n个节点,m对优先级关系,a[i] b[i]代表a[i]的优先级比b[i]高,现在问你至少需要前多少对关系就能确定所有节点的优先级: #include <iostream> #i ...