Java中Future和FutureTask的示例详解及使用
作者:Charte 发布时间:2023-01-29 11:48:42
一、Future 接口
当 call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用 Future 对象。
将 Future 视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦Callable 返回)。Future 基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写 5 种方法,这里列出了重要的方法,如下:
public boolean isDone()
public boolean cancel(boolean mayInterruptIfRunning)
用于停止任务。如果尚未启动,它将停止任务。如果已启动,则仅在 mayInterrupt 为 true时才会中断任务。
boolean isCancelled()
如果任务在正常结束之前被取消返回true
public V get() throws InterruptedException, ExecutionException
用于获取任务的结果。如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException
如果任务完成,则返回 true,否则返回 false。
Callable 与 Runnable 类似,因为它封装了要在另一个线程上运行的任务,而 Future 用于存储从另一个线程获得的结果。
实际上,Future 也可以与 Runnable 一起使用。要创建线程,需要 Runnable。为了获得结果,需要 future。
二、FutureTask
介绍:当一个线程需要等待另一个线程把某个任务执行完后它才能继续执行,此时可以使用FutureTask。假设有多个线程执行若干任务,每个任务最多只能被执行一次。当多个线程试图同时执行同一个任务时,只允许一个线程执行任务,其他线程需要等待这个任务执行完后才能继续执行。
Java 库具有具体的 FutureTask 类型,该类型实现 Runnable 和 Future,并方便地将两种功能组合在一起。 可以通过为其构造函数提供 Callable 来创建FutureTask。然后,将 FutureTask 对象提供给 Thread 的构造函数以创建Thread 对象。因此,间接地使用 Callable 创建线程。
FutureTask状态转换
FutureTask有以下7种状态:
FutureTask任务的运行状态,最初为NEW。运行状态仅在set、setException和cancel方法中转换为终端状态。在完成过程中,状态可能呈现出瞬时值INTERRUPTING(仅在中断运行程序以满足**cancel(true)**的情况下)或者COMPLETING(在设置结果时)状态时。从这些中间状态到最终状态的转换使用成本更低的有序/延迟写,因为值是统一的,需要进一步修改。
state:表示当前任务的运行状态,FutureTask的所有方法都是围绕state开展的,state声明为volatile,保证了state的可见性,当对state进行修改时所有的线程都会看到。
NEW:表示一个新的任务,初始状态
COMPLETING:当任务被设置结果时,处于COMPLETING状态,这是一个中间状态。
NORMAL:表示任务正常结束。
EXCEPTIONAL:表示任务因异常而结束
CANCELLED:任务还未执行之前就调用了cancel(true)方法,任务处于CANCELLED
INTERRUPTING:当任务调用cancel(true)中断程序时,任务处于INTERRUPTING状态,这是一个中间状态。
INTERRUPTED:任务调用cancel(true)中断程序时会调用interrupt()方法中断线程运行,任务状态由INTERRUPTING转变为INTERRUPTED
可能的状态过渡:
1、NEW -> COMPLETING -> NORMAL:正常结束
2、NEW -> COMPLETING -> EXCEPTIONAL:异常结束
3、NEW -> CANCELLED:任务被取消
4、NEW -> INTERRUPTING -> INTERRUPTED:任务出现中断
三、使用 Callable 和 Future
Runnable缺少的一项功能是,当线程终止时(即 run()完成时),我们无法使线程返回结果。
为了支持此功能,Java 中提供了 Callable 接口。不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable。
所以我们可以找一个中间人,也就是FutureTask。
案例
class MyThreadA implements Callable {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName() + "在call方法里");
System.out.println(Thread.currentThread().getName() + "线程进入了 call方法,开始睡觉(进行了一些计算)");
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() + "睡醒了");
return Thread.currentThread().getName() + "返回的:" + System.currentTimeMillis();
}
}
public class demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTaskA = new FutureTask<>(new MyThreadA());
FutureTask<String> futureTaskB = new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName() + "在call方法里");
return Thread.currentThread().getName() + "返回的:" + System.currentTimeMillis();
});
new Thread(futureTaskA,"线程A").start();
new Thread(futureTaskB,"线程B").start();
while (!futureTaskB.isDone()){ //isDone表示FutureTask的计算是否完成
System.out.println("wait.......");
}
System.out.println(futureTaskA.get());
System.out.println(futureTaskB.get());
System.out.println(Thread.currentThread().getName() + "结束了");
}
}
输出结果:
由上图两个线程返回的时间差约等于10秒可以看出,当一个线程(线程B)需要等待(一直wait…)另一个线程(线程A)把某个任务(进行了一些计算)执行完后它才能继续执行,此时可以使用FutureTask。不管futureTaskA.get()和futureTaskB.get()谁在前面,输出结果一定是“线程B返回的:xxx”在“wait…”的后面。假设有多个线程执行若干任务,每个任务最多只能被执行一次。当多个线程试图同时执行同一个任务时,只允许一个线程执行任务,其他线程需要等待这个任务执行完后才能继续执行。
四、小结(FutureTask核心原理)
FutureTask核心原理
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成,当主线程将来需要时,就可以通过 Future对象获得后台作业的计算结果或者执行状态。
• 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果
• 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
• get只计算一次,因此 get 方法放到最后。
附:FutureTask在高并发环境下确保任务只执行一次
网上有篇例子,但是中间讲的不是很清楚。我重新梳理了一下。
在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景,通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下面所示:
package com.concurrency.chapter15;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: 错误示例
*
* @description: 在很多高并发的环境下,往往我们只需要某些任务只执行一次。
* 这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,
* 当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景,
* 通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下
* 在例子中,我们通过加锁确保高并发环境下的线程安全,也确保了connection只创建一次,然而却牺牲了性能。
*
* @author: zhouzhixiang
*
* @create: 2019-05-14 20:22
*/
public class FutureTaskConnection1 {
private static Map<String, Connection> connectionPool = new HashMap<>();
private static ReentrantLock lock = new ReentrantLock();
public static Connection getConnection(String key) {
try {
lock.lock();
Connection connection = connectionPool.get(key);
if (connection == null) {
Connection newConnection = createConnection();
connectionPool.put(key, newConnection);
return newConnection;
}
return connection;
} finally {
lock.unlock();
}
}
private static Connection createConnection() {
try {
return DriverManager.getConnection("");
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
来源:https://blog.csdn.net/weixin_45296116/article/details/121621317
![](https://www.aspxhome.com/images/zang.png)
![](https://www.aspxhome.com/images/jiucuo.png)
猜你喜欢
- 定义与结构 备忘录(Memento)模式又称标记(Token)模式。GOF给备忘录模式的定义为:在不破坏
- 目录@Configuration + @Bean@Componet + @ComponentScan@Import注解导入@Import直接
- 本文实例讲述了C#实现简单的Login窗口。分享给大家供大家参考。具体实现方法如下:C# 制作登录窗体,登录成功之后正确的做法是关闭(clo
- 本文实例为大家分享了java实现简单快递系统的具体代码,供大家参考,具体内容如下创建四个类Express,Locker, User, Adm
- 前一段时间遇到一个问题,是关于读取项目中文件资源的问题。我是一个maven工程 我把一张照片放到resource下面,然后在本地读取的时候可
- mybatis输出SQL格式化通过第三方日志工具可以控制日志级别的输出,但是我们发现mybatis输出的SQL不是那么的完整,我们SQL里的
- 最近滑动验证码在很多网站逐步流行起来,一方面对用户体验来说,比较新颖,操作简单,另一方面相对图形验证码来说,安全性并没有很大的降低。当然到目
- 一、引入其实之前一直以为像饿了么或者是美团外卖那种把商品添加到购物车的动画会很难做,但是实际做起来好像并没有想象中的那么难哈哈。布局主要使用
- 前言这里用swing ,awt写的。我们大概要做成一个电脑的记事本那样的一个编辑器。可以调整字体,字号,颜色。能够打开、保存文件,新建窗口,
- 本篇介绍了SpringBoot 缓存(EhCache 2.x 篇),分享给大家,具体如下:SpringBoot 缓存在 spring Boo
- indexof方法:注解:indexOf 方法返回一个整数值,指出 String 对象内子字符串的开始位置。如果没有找到子字符串,则返回-1
- 剪贴板是Windows操作系统中最常用的功能之一,它用来从一个应用程序向另一个应用程序传递数据,可以是文本,图象,甚至是程序对象。不过剪贴板
- 说明:本文记录如何在Idea下,利用Maven管理项目,并整合SSM(Spring + Spring MVC +Mybatis)框架,实现简
- 简单的页面分析在上一个文章简单的数据库连接测试,已经测试和数据库做简单的交互,也就是dao层的实现,接下来要说的却是action的简单实现,
- 目录前言方案一: 数组方案二:HashMap由 key 获取 value由 value 获取 key解决方案三:枚举总结前言开发系统一些状态
- 一、目标效果聊天会话页的列表效果1、聊天数据不满一屏时,顶部显示所有聊天数据2、插入消息时如果最新消息紧靠列表底部时,则插入消息会使列表向上
- 前言本文将重点讲解一下Spring中@PropertySource注解的使用,如何通过PropertySource注解加载指定的配置文件。以
- 首先来说一说该指南针的实现思路:程序先准备一张指南针图片,该图片上方向指针指向北方。接下来开发一个检测方向的传感器,程序检测到手机顶部绕Z轴
- 本文实例讲述了Android使用ListView批量删除item的方法。分享给大家供大家参考,具体如下:利用CheckBox选中一个或多个i
- 上一篇文章谈到音频剪切、混音、拼接与转码,也详细介绍cMake配置与涉及FFmpeg文件的导入: android端采用FFmpeg进行音频混