通过前面几篇的学习,我们知道创建线程的方式有两种,一种是实现Runnable接口,另一种是继承Thread,但是这两种方式都有个缺点,那就是在任务执行完成之后无法获取返回结果,那如果我们想要获取返回结果该如何实现呢?还记上一篇Executor框架结构中提到的Callable接口和Future接口吗?,是的,从Java SE 5.0开始引入了Callable和Future,通过它们构建的线程,在任务执行完成后就可以获取执行结果,今天我们就来聊聊线程创建的第三种方式,那就是实现Callable接口。

1.Callable<V>接口
我们先回顾一下java.lang.Runnable接口,就声明了run(),其返回值为void,当然就无法获取结果了。
  1. public interface Runnable {
  2. public abstract void run();
  3. }

而Callable的接口定义如下

  1. public interface Callable<V> {
  2. V   call()   throws Exception;
  3. }

该接口声明了一个名称为call()的方法,同时这个方法可以有返回值V,也可以抛出异常。嗯,对该接口我们先了解这么多就行,下面我们来说明如何使用,前篇文章我们说过,无论是Runnable接口的实现类还是Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行,ThreadPoolExecutor或ScheduledThreadPoolExecutor都实现了ExcutorService接口,而因此Callable需要和Executor框架中的ExcutorService结合使用,我们先看看ExecutorService提供的方法:

  1. <T> Future<T> submit(Callable<T> task);
  2. <T> Future<T> submit(Runnable task, T result);
  3. Future<?> submit(Runnable task);
第一个方法:submit提交一个实现Callable接口的任务,并且返回封装了异步计算结果的Future。
第二个方法:submit提交一个实现Runnable接口的任务,并且指定了在调用Future的get方法时返回的result对象。
第三个方法:submit提交一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。
因此我们只要创建好我们的线程对象(实现Callable接口或者Runnable接口),然后通过上面3个方法提交给线程池去执行即可。还有点要注意的是,除了我们自己实现Callable对象外,我们还可以使用工厂类Executors来把一个Runnable对象包装成Callable对象。Executors工厂类提供的方法如下:
  1. public static Callable<Object> callable(Runnable task)
  2. public static <T> Callable<T> callable(Runnable task, T result)
2.Future<V>接口
Future<V>接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。我们看看Future接口的源码:
  1. public interface Future<V> {
  2. boolean cancel(boolean mayInterruptIfRunning);
  3. boolean isCancelled();
  4. boolean isDone();
  5. V get() throws InterruptedException, ExecutionException;
  6. V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
  7. }
方法解析:
V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将返回null。
boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
boolean isCanceller() :如果任务完成前被取消,则返回true。
boolean cancel(boolean mayInterruptRunning) :如果任务还没开始,执行cancel(...)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(...)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。
通过方法分析我们也知道实际上Future提供了3种功能:(1)能够中断执行中的任务(2)判断任务是否执行完成(3)获取任务执行完成后额结果。
但是我们必须明白Future只是一个接口,我们无法直接创建对象,因此就需要其实现类FutureTask登场啦。
3.FutureTask类
我们先来看看FutureTask的实现
  1. public class FutureTask<V> implements RunnableFuture<V> {

FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:

  1. public interface RunnableFuture<V> extends Runnable, Future<V> {
  2. void run();
  3. }
分析:FutureTask除了实现了Future接口外还实现了Runnable接口,因此FutureTask也可以直接提交给Executor执行。 当然也可以调用线程直接执行(FutureTask.run())。接下来我们根据FutureTask.run()的执行时机来分析其所处的3种状态:
(1)未启动,FutureTask.run()方法还没有被执行之前,FutureTask处于未启动状态,当创建一个FutureTask,而且没有执行FutureTask.run()方法前,这个FutureTask也处于未启动状态。
(2)已启动,FutureTask.run()被执行的过程中,FutureTask处于已启动状态。
(3)已完成,FutureTask.run()方法执行完正常结束,或者被取消或者抛出异常而结束,FutureTask都处于完成状态。


下面我们再来看看FutureTask的方法执行示意图(方法和Future接口基本是一样的,这里就不过多描述了)

分析:
(1)当FutureTask处于未启动或已启动状态时,如果此时我们执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或者抛出异常。
(2)当FutureTask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会执行。
当FutureTask处于已启动状态时,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果任务取消成功,cancel(...)返回true;但如果执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时cancel(...)返回false。
当任务已经完成,执行cancel(...)方法将返回false。
最后我们给出FutureTask的两种构造函数:
  1. public FutureTask(Callable<V> callable) {
  2. }
  3. public FutureTask(Runnable runnable, V result) {
  4. }
3.Callable<V>/Future<V>/FutureTask的使用
通过上面的介绍,我们对Callable,Future,FutureTask都有了比较清晰的了解了,那么它们到底有什么用呢?我们前面说过通过这样的方式去创建线程的话,最大的好处就是能够返回结果,加入有这样的场景,我们现在需要计算一个数据,而这个数据的计算比较耗时,而我们后面的程序也要用到这个数据结果,那么这个时Callable岂不是最好的选择?我们可以开设一个线程去执行计算,而主线程继续做其他事,而后面需要使用到这个数据时,我们再使用Future获取不就可以了吗?下面我们就来编写一个这样的实例
3.1 使用Callable+Future获取执行结果
Callable实现类如下:
  1. package com.zejian.Executor;
  2. import java.util.concurrent.Callable;
  3. /**
  4. * @author zejian
  5. * @time 2016年3月15日 下午2:02:42
  6. * @decrition Callable接口实例
  7. */
  8. public class CallableDemo implements Callable<Integer> {
  9. private int sum;
  10. @Override
  11. public Integer call() throws Exception {
  12. System.out.println("Callable子线程开始计算啦!");
  13. Thread.sleep(2000);
  14. for(int i=0 ;i<5000;i++){
  15. sum=sum+i;
  16. }
  17. System.out.println("Callable子线程计算结束!");
  18. return sum;
  19. }
  20. }

Callable执行测试类如下:

  1. package com.zejian.Executor;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.Future;
  5. /**
  6. * @author zejian
  7. * @time 2016年3月15日 下午2:05:43
  8. * @decrition callable执行测试类
  9. */
  10. public class CallableTest {
  11. public static void main(String[] args) {
  12. //创建线程池
  13. ExecutorService es = Executors.newSingleThreadExecutor();
  14. //创建Callable对象任务
  15. CallableDemo calTask=new CallableDemo();
  16. //提交任务并获取执行结果
  17. Future<Integer> future =es.submit(calTask);
  18. //关闭线程池
  19. es.shutdown();
  20. try {
  21. Thread.sleep(2000);
  22. System.out.println("主线程在执行其他任务");
  23. if(future.get()!=null){
  24. //输出获取到的结果
  25. System.out.println("future.get()-->"+future.get());
  26. }else{
  27. //输出获取到的结果
  28. System.out.println("future.get()未获取到结果");
  29. }
  30. } catch (Exception e) {
  31. e.printStackTrace();
  32. }
  33. System.out.println("主线程在执行完成");
  34. }
  35. }
执行结果:

Callable子线程开始计算啦!
主线程在执行其他任务
Callable子线程计算结束!
future.get()-->12497500
主线程在执行完成
3.2 使用Callable+FutureTask获取执行结果
  1. package com.zejian.Executor;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.Future;
  5. import java.util.concurrent.FutureTask;
  6. /**
  7. * @author zejian
  8. * @time 2016年3月15日 下午2:05:43
  9. * @decrition callable执行测试类
  10. */
  11. public class CallableTest {
  12. public static void main(String[] args) {
  13. //      //创建线程池
  14. //      ExecutorService es = Executors.newSingleThreadExecutor();
  15. //      //创建Callable对象任务
  16. //      CallableDemo calTask=new CallableDemo();
  17. //      //提交任务并获取执行结果
  18. //      Future<Integer> future =es.submit(calTask);
  19. //      //关闭线程池
  20. //      es.shutdown();
  21. //创建线程池
  22. ExecutorService es = Executors.newSingleThreadExecutor();
  23. //创建Callable对象任务
  24. CallableDemo calTask=new CallableDemo();
  25. //创建FutureTask
  26. FutureTask<Integer> futureTask=new FutureTask<>(calTask);
  27. //执行任务
  28. es.submit(futureTask);
  29. //关闭线程池
  30. es.shutdown();
  31. try {
  32. Thread.sleep(2000);
  33. System.out.println("主线程在执行其他任务");
  34. if(futureTask.get()!=null){
  35. //输出获取到的结果
  36. System.out.println("futureTask.get()-->"+futureTask.get());
  37. }else{
  38. //输出获取到的结果
  39. System.out.println("futureTask.get()未获取到结果");
  40. }
  41. } catch (Exception e) {
  42. e.printStackTrace();
  43. }
  44. System.out.println("主线程在执行完成");
  45. }
  46. }
执行结果:
Callable子线程开始计算啦!
主线程在执行其他任务
Callable子线程计算结束!
futureTask.get()-->12497500
主线程在执行完成

Java多线程编程:Callable、Future和FutureTask浅析的更多相关文章

  1. Java 并发编程——Callable+Future+FutureTask

    Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...

  2. Java多线程编程中Future模式的详解

    Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...

  3. Java多线程编程中Future模式的详解<转>

    Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...

  4. java 多线程:Callable接口;FutureTask类实现对象【Thread、Runnable、Callable三种方式实现多线程的区别】

    Callable接口介绍: Java5开始,Java提供了Callable接口,像是Runnable接口的增强版,Callable接口提供了一个 call()方法可以作为线执行体. call()方法比 ...

  5. Java多线程的Callable, Future, FutureCallback

    Callable可以看成是一个增强版的Runnable, 带返回结果, 需要通过Future或者FutureTask来提交任务或运行线程, 然后通过Future/FutureTask的get方法得到返 ...

  6. Java多线程:Callable,Future,FutureTask

    一.Future Future和Callable基本是成对出现的,Callable负责产生结果,Future负责获取结果.     1.Callable接口类似于Runnable,只是Runnable ...

  7. Java多线程编程:Callable、Future和FutureTask浅析(多线程编程之四)

    java多线程-概念&创建启动&中断&守护线程&优先级&线程状态(多线程编程之一)java多线程同步以及线程间通信详解&消费者生产者模式&死锁& ...

  8. Java - 32 Java 多线程编程

    Java 多线程编程 Java给多线程编程提供了内置的支持.一个多线程程序包含两个或多个能并发运行的部分.程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径. 多线程是多任务的一种特别 ...

  9. Java多线程编程(学习笔记)

    一.说明 周末抽空重新学习了下多线程,为了方便以后查阅,写下学习笔记. 有效利用多线程的关键是理解程序是并发执行而不是串行执行的.例如:程序中有两个子系统需要并发执行,这时候需要利用多线程编程. 通过 ...

  10. Java-Runoob-高级教程: Java 多线程编程

    ylbtech-Java-Runoob-高级教程: Java 多线程编程 1.返回顶部 1. Java 多线程编程 Java 给多线程编程提供了内置的支持. 一条线程指的是进程中一个单一顺序的控制流, ...

随机推荐

  1. Android -- Drag&&Drop

    Android3.0提供了drag/drop框架,利用此框架可以实现使用拖放手势将一个view拖放到当前布局中的另外一个view中. 实现拖放的步骤 首先,我们先了解一下拖放过程,从官方文档可以知道, ...

  2. 【Scala】Scala-case-参考资料

    Scala-case-参考资料 scala case_百度搜索 Scala School - 基础知识(续) scala case匹配值 - CSDN博客 scala入门教程:scala中的match ...

  3. 成为Linux内核高手的四个方法

    首页 最新文章 资讯 程序员 设计 IT技术 创业 在国外 营销 趣文 特别分享 更多 > - Navigation -首页最新文章资讯程序员设计IT技术- Java & Android ...

  4. IIS 7.5: HOW TO ENABLE TLS 1.1 AND TLS 1.2

    In IIS 7.5, which is installed on Windows 2008 R2 servers, only SSL 3.0 and TLS 1.0 are enabled for ...

  5. ERROR in index.web.js from UglifyJs

    使用weexpack构建weex应用时,npm run serve一直报这个错误 ERROR in index.web.js from UglifyJs Unexpected token: name ...

  6. mac 苹果鼠标 magic mouse2 当触摸代替点击当触摸板教程

    本文解决 mac 苹果鼠标 magic mouse2 触摸代替点击,鼠标当触摸板教程 买了magic mouse2之后,发现官方不推荐使用触摸代替点击,我感觉很不爽,这不就是一个触摸板嘛,于是各种搜软 ...

  7. TCP/IP具体解释学习笔记--TCP的超时与重传

    1.基本概念 TCP之所以能够安全的将数据在传输中的安全性,是因为它每次给对方发送数据,都会等待对方给个确认,当长时间收不到这个确认,发送端就会重发这个数据. 2.超时时间的測量 要測超时时间,TCP ...

  8. 配置 Windows 下的 nodejs C++ 模块编译环境 安装 node-gyp

    配置 Windows 下的 nodejs C++ 模块编译环境 根据 node-gyp 指示的 Windows 编译环境说明, 简单一句话就是 "Python + VC++ 编译环境&quo ...

  9. OpenWRT使用wifidog实现强制认证的WIFI热点

    首先安装wifidog到OpenWRT的路由器: opkg update opkg install wifidog wifidog依赖下面这些模块: iptables-mod-extra iptabl ...

  10. 解决Android Studio无法下载sdk的问题

    因为google被墙了,android sdk无法下载.然后各种百度,都是说让设置代理,给的代理地址一般都是用的下面这个代理服务器: 大连东软信息学院镜像服务器地址: mirrors.neusoft. ...