JAVA多线程高并发学习笔记(三)——Callable、Future和FutureTask
为什么要是用Callable和Future
Runnable的局限性
Executor采用Runnable作为基本的表达形式,虽然Runnable的run方法能够写入日志,写入文件,写入数据库等操作,但是它不能返回一个值,或者抛出一个受检查的异常,有些需要返回值的需求就不能满足了。
能够取消
Executor中的任务有四个状态:创建,提交,开始和完成。如果说有些任务执行时间比较长,希望能够取消该任务,Executor中的任务在未开始前是可以取消的,如果已经开始了,只能通过中断的方式来取消。如果使用Callable和Future的结合,可以使用Future的canel方法取消任务,这样就方便多了。
一个例子:
import java.util.concurrent.*;
public class Demo1 {
public static void main(String args[]) throws Exception {
ServiceTask task = new ServiceTask();
ExecutorService executor = Executors.newCachedThreadPool();
Future<Integer> result = executor.submit(task);
executor.shutdown();
System.out.println("正在执行任务");
Thread.sleep(1000);
System.out.println("task运行结果为:" + result.get());
}
}
class ServiceTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(2000);
int result = 0;
// 假设一个很庞大的计算
for(int i=1;i<100;i++){
for (int j=0;j<i;j++){
result +=j;
}
}
return result;
}
}
看一下执行结果:

这个例子就是一个非常简单的使用Callable和Futute的例子,ServiceTask类实现了Callable接口,并返回一个Integer类型的值。
Future<Integer> result = executor.submit(task);这行代码就是构造一个Future。使用其get()方法就能得到最后的运行值。
好了看完这一个简单的例子,那就来仔细了解一下它们。
了解Callable和Future
Callable
来看一下callable的代码:
public abstract interface Callable<V> {
public abstract V call() throws Exception;
}
可以看出它是接口,提到接口就可以明白接口是灵活的,支持传入泛型参数。这个没什么,我们来重点介绍一下Future
Future
首先来看关于它的介绍
Future提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。
来看一下Future的代码
public abstract interface Future<V> {
public abstract boolean cancel(boolean paramBoolean);
public abstract boolean isCancelled();
public abstract boolean isDone();
public abstract V get() throws InterruptedException, ExecutionException;
public abstract V get(long paramLong, TimeUnit paramTimeUnit)
throws InterruptedException, ExecutionException, TimeoutException;
}
提供了五个方法
public abstract boolean cancel(boolean paramBoolean)
试图取消任务的执行(注意是试图),因为存在一些任务已完成、已取消或者因为某些原因无法取消的因素,存在着取消失败的可能性。
当canel方法起作用时,有两个情况:
1.任务未开始,则该任务将永远不会运行;
2.任务处于执行状态,paramBoolean表示是否采用中断的方式中断线程。
public abstract boolean isCancelled()
如果任务正常取消的,则返回true。
public abstract boolean isDone();
如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。
(注意如果调用isCanle方法,那么isDone将始终返回true).
public abstract V get() throws InterruptedException, ExecutionException;
重点到了!这是Future获取计算结果的方式之一,使用get方法。(注意这里返回的是Callable中的泛型)
get方法取决于任务的状态(未开始,运行中,已完成),如果任务已经完成,那么get会立即返回或者抛出一个异常;
如果任务没有完成,那么get将阻塞知道任务完成。如果任务抛出了异常,那么get会将该异常封装成ExecutionException抛出。
public abstract V get(long paramLong, TimeUnit paramTimeUnit)
throws InterruptedException, ExecutionException, TimeoutException;
如果需要在给定时间后获取计算结果,可以使用这个方法,如果超过给定时间之后没有得到计算结果,则抛出TimeoutException。(注意这里返回的是Callable中的泛型)
如何使用
来看代码:
import java.util.concurrent.*;
public class Demo1 {
public static void main(String args[]) throws Exception {
// 1.先实例化任务对象
ServiceTask task = new ServiceTask();
// 2.实例化Executor框架中的线程池
ExecutorService executor = Executors.newCachedThreadPool();
// 3.使用submit方法将任务提交(返回的是一个Future)
Future<Integer> result = executor.submit(task);
// 4.记得关闭线程池
executor.shutdown();
System.out.println("正在执行任务");
Thread.sleep(1000);
// 5.打印最后的结果
System.out.println("task运行结果为:" + result.get());
}
}
/**
* Callable的实现类
*/
class ServiceTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(2000);
int result = 0;
// 假设一个很庞大的计算
for(int i=1;i<100;i++){
for (int j=0;j<i;j++){
result +=j;
}
}
return result;
}
}
运行结果:

接下来我们来试一下定时取结果:
还是在原来的代码上修改:
import java.util.concurrent.*;
public class Demo1 {
public static void main(String args[]) throws Exception {
// 1.先实例化任务对象
ServiceTask task = new ServiceTask();
// 2.实例化Executor框架中的线程池
ExecutorService executor = Executors.newCachedThreadPool();
// 3.使用submit方法将任务提交(返回的是一个Future)
Future<Integer> result = executor.submit(task);
// 4.记得关闭线程池
executor.shutdown();
System.out.println("正在执行任务");
Thread.sleep(1000);
// 5.设置定时一秒取结果
System.out.println("task运行结果为:" + result.get(1,TimeUnit.MILLISECONDS));
}
}
/**
* Callable的实现类
*/
class ServiceTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
//这里睡眠2秒
Thread.sleep(2000);
int result = 0;
// 假设一个很庞大的计算
for(int i=1;i<100;i++){
for (int j=0;j<i;j++){
result +=j;
}
}
return result;
}
}
来提前猜想一下,首先设置了定时一秒之后取得结果,但是ServiceTask设置两秒的睡眠时间,理应取结果失败,看一下运行结果:

是的,如果在规定时间内无法取到结果,就会返回TimeoutException。
谈谈FutureTask
FutureTask是Future的实现类,它继承了RunnableFuture,RunnableFuture实际上继承了Runnable和Future接口。
来看一下使用如何FutureTask:
import java.util.concurrent.*;
public class FutureCallDemo2 {
public static void main(String args[])throws Exception{
// 1.先实例化任务对象
FutureTaskService task = new FutureTaskService();
// 2.实例化Executor框架中的线程池
ExecutorService excutor = Executors.newCachedThreadPool();
// 3.直接new一个FutureTask
FutureTask<Long> result = new FutureTask<Long>(task);
// 4.提交任务
excutor.submit(result);
// 5.关闭线程池
excutor.shutdown();
System.out.println("主线程正在执行任务");
System.out.println("task运行结果为:" + result.get());
}
}
/**
* 继承Callable接口
*/
class FutureTaskService implements Callable<Long> {
@Override
public Long call() throws Exception {
Thread.sleep(3000);
// 10的阶乘
long sum = 1;
for (int i = 1; i <= 10; i++) {
sum = sum * i;
}
return sum;
}
}
用法的话其实差不多。
总结:
Future和Callable可以实现异构任务,但是有很多值得考虑的地方。
比如一个类使用了两个任务,一个负责渲染页面,一个负责下载图像。
伪代码如下:
//通过获取图像
List<ImageData>ImageDataList = future.get();
for(ImageData data:ImageDataList ){
//渲染页面
renderPage(data);
}
看似并行的执行任务,但是却存在着问题。如果说下载图像的速度远小于渲染页面的速度,那么最终的执行速度就和串行无异了。
所以只有当大量相互独立且同构的任务可以进行并发处理时,才能体现出将任务分到多个任务中带来的性能提升,考虑实际情况再选择使用会带来事半功倍的效果。
本文参考:
Java并发编程实战
JAVA多线程高并发学习笔记(三)——Callable、Future和FutureTask的更多相关文章
- Java多线程高并发学习笔记(三)——深入理解线程池
线程池最核心的一个类:ThreadPoolExecutor. 看一下该类的构造器: public ThreadPoolExecutor(int paramInt1, int paramInt2, lo ...
- Java多线程高并发学习笔记(一)——Thread&Runnable
进程与线程 首先来看百度百科关于进程的介绍: 进程是一个具有独立功能的程序关于某个数据集合的一次运行活动.它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体.它不只是程序的代码,还包括当前的 ...
- Java多线程高并发学习笔记(二)——深入理解ReentrantLock与Condition
锁的概念 从jdk发行1.5版本之后,在原来synchronize的基础上,增加了重入锁ReentrantLock. 本文就不介绍synchronize了,有兴趣的同学可以去了解一下,本文重点介绍Re ...
- Java多线程高并发学习笔记——阻塞队列
在探讨可重入锁之后,接下来学习阻塞队列,这边篇文章也是断断续续的写了很久,因为最近开始学ssm框架,准备做一个自己的小网站,后续可能更新自己写网站的技术分享. 请尊重作者劳动成果,转载请标明原文链接: ...
- Java 多线程高并发编程 笔记(一)
本篇文章主要是总结Java多线程/高并发编程的知识点,由浅入深,仅作自己的学习笔记,部分侵删. 一 . 基础知识点 1. 进程于线程的概念 2.线程创建的两种方式 注:public void run( ...
- JAVA 多线程和并发学习笔记(三)
Java并发编程中使用Executors类创建和管理线程的用法 1.类 Executors Executors类可以看做一个“工具类”.援引JDK1.6 API中的介绍: 此包中所定义的 Execut ...
- JAVA 多线程和并发学习笔记(二)
一.Java中创建线程方法 1. 继承Thread类创建线程类 定义Thread类的子类,重写该类的run()方法.该方法为线程执行体. 创建Thread子类的实例.即线程对象. 调用线程对象的sta ...
- Java 多线程高并发编程 笔记(二)
1. 单例模式(在内存之中永远只有一个对象) 1.1 多线程安全单例模式——不使用同步锁 public class Singleton { private static Singleton sin=n ...
- JAVA 多线程和并发学习笔记(四)
1. 多进程 实现并发最直接的方式是在操作系统级别使用进程,进程是运行在它自己的地址空间内的自包容的程序.多任务操作系统可以通过周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个进程. 尽 ...
随机推荐
- 【Java集合学习】HashMap源码之“拉链法”散列冲突的解决
1.HashMap的概念 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射. HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io ...
- RDVECore来自锐动的无UI,高度抽象化API的视频编辑SDK
1 编写目的 预期读者: 有视频编辑开发经验或者无经验的,打算或者正在使用"锐动IOS版RDVECore"的相关工程师. iOS软件工程师. 产品经理. QA 2 名词解释 分辨率 ...
- C3P0数据库连接池使用中的问题
java.io.FileNotFoundException: D:\javaStudy\javaee\.metadata\.plugins\org.eclipse.wst.server.core\tm ...
- 如何生成转储(dmp)文件--工具篇
转储文件也就是我们常说的dump文件.可以把转储文件看成软件的某个时刻的一个快照.转储文件一般都是在软件出现问题时手动生成或者程序自动生成.下面我们介绍几种生成转储文件的方法. 1. 任务管理器 任务 ...
- 从零构建一个react+webpack+typescript的应用
今天要完成在windows下从零开始构建一个react应用的任务 首先,新建一个文件夹,然后在该文件夹下使用命令npm init 初始化一个node项目. 然后安装所需依赖, npm i react ...
- 权限大全-linux基础
一.文件权限 (1)文件的权限主要针对三类对象定义: u:属主,owner g:属组,group o:其他,other 权限有三种: r:可读 w:可写 x:可执行 ...
- Java温故而知新-冒泡法排序
冒泡法排序是各种初学者在学习数组与循环结构时都会练习的一种简单排序算法. 冒泡法的精髓在于比较相邻的两个元素,较大的元素会不断的排到队伍后面去,就像水里的泡泡一样不断向上跑. 想像一下倒在一个透明玻璃 ...
- TensorFlow框架(5)之机器学习实践
1. Iris data set Iris数据集是常用的分类实验数据集,由Fisher, 1936收集整理.Iris也称鸢尾花卉数据集,是一类多重变量分析的数据集.数据集包含150个数据集,分为3类, ...
- Django创建模板、URL模式、创建视图函数
1.在应用目录下创建模板(templates目录) 在模板目录下创建archive.html <!DOCTYPE html> <html lang="en"> ...
- Celery基本原理探讨
本文对Celery进行了研究,由于其实现相对比较复杂没有足够的时间和精力对各方各面的源码进行分析,因此本文根据Celery的使用方法以及实际行为分析其运行原理,并根据查阅相关代码进行了一定程度的验证. ...