目录
在Java中,创建线程一般有两种方式,一种是继承Thread类,一种是实现Runnable接口。然而,这两种方式的缺点是在线程任务执行结束后,无法获取执行结果。一般只能采用共享变量或共享存储区以及线程通信的方式实现获得任务结果,也可以使用回调的方法:
package com.thread.myfuture;
interface Callable {
void call(int num);
}
public class FutureTest {
public static void main(String[] args) {
System.out.println("主线程开始");
final Callable callable = new Callable() {
@Override
public void call(int num) {
System.out.println("线程运行结果值 num=="+num);
}
};
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+" 开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
callable.call(100);
System.out.println("线程"+Thread.currentThread().getName()+" 结束");
}
}, "t1").start();
System.out.println("主线程结束");
}
}
结果:
使用回调方法的缺点:
- 必须要创建回调接口。而且线程运行中可能产生异常,那么回调接口至少包含成功回调和错误回调两个方法。
- 当线程运行后,只能等到线程回调接口,本身我们没有办法进行取消操作。
- 如果要重复获取同样线程运行结果的值,还是只能重新运行线程。当然你也可以使用一个变量缓存结果值。
一、Callable
1.1 介绍
可以使用Callable和Future来实现获取任务结果的操作。Callable用来执行任务,产生结果,而Future用来获得结果。
Callable接口与Runnable接口是否相似,查看源码,可知Callable接口的定义如下:
可以看到,与Runnable接口不同之处在于,call方法带有泛型返回值V。
1.2 Future常用方法
V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。
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)获取任务执行完成后的结果。
1.3 Demo
package com.thread.pool;
import java.util.concurrent.*;
/**
* @Author: 98050
* @Time: 2018-12-07 21:05
* @Feature: Callable
*/
public class Test007 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
System.out.println("主线程开始执行");
final Future<String> submit = executorService.submit(new TaskCallable());
String result = submit.get();
System.out.println("执行结果:"+result);
executorService.shutdown();
}
}
class TaskCallable implements Callable<String>{
public String call() throws Exception {
System.out.println("正在执行任务,需要等待五秒,开始执行");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+i);
Thread.sleep(1000);
}
System.out.println("执行结束");
return "success";
}
}
结果:
1.4 Runnable和Callable的区别
(1)Callable规定的方法是call(),Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如果线程没有执行完,Future.get()方法可能会阻塞当前线程的执行;如果线程出现异常,Future.get()会throws InterruptedException或者ExecutionException;如果线程已经取消,会跑出CancellationException。取消由cancel 方法来执行。isDone确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明Future<?> 形式类型、并返回 null 作为底层任务的结果。
二、Future模式
2.1 介绍
Future模式的核心在于:去除了主函数的等待时间,并使得原本需要等待的时间段可以用于处理其他业务逻辑
Futrure模式:对于多线程,如果线程A要等待线程B的结果,那么线程A没必要等待B,直到B有结果,可以先拿到一个未来的Future,等B有结果是再取真实的结果。
例子:网络图片的下载。刚开始是通过模糊的图片来代替最后的图片,等下载图片的线程下载完图片后在替换。而在这个过程中可以做一些其他的事情。
首先客户端向服务器请求RealSubject,但是这个资源的创建是非常耗时的,怎么办呢?这种情况下,首先返回Client一个FutureSubject,以满足客户端的需求,于此同时呢,Future会通过另外一个Thread 去构造一个真正的资源,资源准备完毕之后,在给future一个通知。如果客户端急于获取这个真正的资源,那么就会阻塞客户端的其他所有线程,等待资源准备完毕。
2.2 实现
参 与 者 | 作 用 |
Main | 系统启动,调用Client发出请求 |
Client | 返回Data对象,立即返回FutureData,并开启ClientThread线程装配RealData |
Data | 返回数据的接口 |
FutureData | Future数据,构造很快,但是是一个虚拟的数据,需要装配RealData |
RealData | 真实数据,其构造是比较慢的 |
2.2.1 公共数据接口
package com.thread.myfuture;
/**
* @Author: 98050
* @Time: 2018-12-07 23:05
* @Feature:
*/
public interface Data {
/**
* 获取请求
* @return
*/
String getRequest();
}
2.2.2 RealData
在这里面实现正在的数据读取,然后保存在result中进行返回。
package com.thread.myfuture;
/**
* @Author: 98050
* @Time: 2018-12-07 23:07
* @Feature:
*/
public class RealData implements Data {
private String result;
public RealData(String data) throws InterruptedException {
System.out.println("正在使用data:"+data+"网络请求数据,耗时需要等待");
Thread.sleep(3000);
System.out.println("读取数据成功,获取结果。。。。。");
result = "success";
}
@Override
public String getRequest() {
return result;
}
}
2.2.3 FutureData
主要功能是当有线程想要获取RealData的时候,程序就会被阻塞,直到RealData被注入后才能使用getRequest方法,注意在getRequest方法中,通过realDara.getRequest()来获取真正的数据。
package com.thread.myfuture;
/**
* @Author: 98050
* @Time: 2018-12-07 23:06
* @Feature:
*/
public class FutureData implements Data {
public volatile static boolean FLAG = false;
private RealData realData;
public synchronized void setRealData(RealData realData){
//如果已经获取到结果,直接返回
if (FLAG){
return;
}
//如果没有获取到数据,传递真实对象
this.realData = realData;
FLAG = true;
notify();
}
@Override
public synchronized String getRequest() {
while (!FLAG){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//获取到数据,直接返回
return realData.getRequest();
}
}
2.2.4 FutureClient
主要作用就是往futureData中注入realData。
package com.thread.myfuture;
/**
* @Author: 98050
* @Time: 2018-12-07 23:15
* @Feature:
*/
public class FutureClient {
public Data request(final String queryStr){
final FutureData futureData = new FutureData();
new Thread(new Runnable() {
public void run() {
try {
RealData realData = new RealData(queryStr);
futureData.setRealData(realData);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return futureData;
}
}
2.2.5 调用者
package com.thread.myfuture;
/**
* @Author: 98050
* @Time: 2018-12-07 23:17
* @Feature:
*/
public class Test {
public static void main(String[] args) {
FutureClient futureClient = new FutureClient();
final Data request = futureClient.request("请求参数........");
System.out.println("请求发送成功!");
String result = request.getRequest();
System.out.println("获取到的结果:"+result);
System.out.println("继续执行其它任务");
}
}
2.2.6 结果
三、Java内置Future模式实现
由于Future是非常常用的多线程设计模式,因此在JDK中内置了Future模式的实现。这些类在java.util.concurrent包里面。其中最为重要的是FutureTask类,它实现了Runnable接口,作为单独的线程运行。在其run()方法中,通过Sync内部类调用Callable接口,并维护Callable接口的返回对象。当使用FutureTask.get()方法时,将返回Callable接口的返回对象。其核心结构图如下所示:
3.1 修改RealData
实现Callable接口,实现具体的业务逻辑。
package com.thread.future;
import java.util.concurrent.Callable;
/**
* @Author: 98050
* @Time: 2018-12-07 23:07
* @Feature:
*/
public class RealData implements Callable<String> {
private String data;
public RealData(String data) {
this.data = data;
}
@Override
public String call() throws Exception {
System.out.println("正在使用:"+data+"........请求数据,耗时等待中");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+i);
Thread.sleep(1000);
}
return "success";
}
}
3.2 修改Main方法
由于使用了JDK的内置框架,Data、FutureData等对象就不再需要了。在Main方法的实现中,直接通过RealData构造FutureTask,并将其作为单独的线程运行。在提交请求后,执行其他业务逻辑,最后通过FutureTask.get()方法,得到RealData的执行结果。
package com.thread.future;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
/**
* @Author: 98050
* @Time: 2018-12-07 23:17
* @Feature:
*/
public class Test {
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<String>(new RealData("参数"));
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(futureTask);
System.out.println("请求完毕");
try {
//执行其它工作
System.out.println("开始执行其它任务,耗时3秒");
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+i);
Thread.sleep(1000);
}
System.out.println("请求结果:"+futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}