代码已上传到Github,有兴趣的同学可以下载看看:https://github.com/ylw-github/Java-ThreadDemo
在Java中,创建线程一般有两种方式,一种是继承Thread类,一种是实现Runnable接口。然而,这两种方式的缺点是在线程任务执行结束后,无法获取执行结果。我们一般只能采用共享变量或共享存储区以及线程通信的方式实现获得任务结果的目的。
不过,Java中,也提供了使用Callable和Future来实现获取任务结果的操作。Callable用来执行任务,产生结果,而Future用来获得结果。
Callable接口与Runnable接口是否相似,查看源码,可知Callable接口的定义如下:
可以看到,与Runnable接口不同之处在于,call方法带有泛型返回值V。
1. 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)获取任务执行完成后的结果。
我们通过简单的例子来体会使用Callable和Future来获取任务结果的用法:
import java.util.concurrent.*; public class FutureDemo { public static class AddNumberTask implements Callable<Integer> { public AddNumberTask() { } @Override public Integer call() throws Exception { System.out.println(System.currentTimeMillis() + ":AddNumberTask ---> call()"); Thread.sleep(5000); return 5000; } } public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newCachedThreadPool(); Future<Integer> future = executor.submit(new AddNumberTask()); System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "线程执行其他任务"); Integer integer = future.get(); System.out.println(System.currentTimeMillis() + ":" + integer); // 关闭线程池 if (executor != null) { executor.shutdown(); } } }
运行结果:
2. Future模式
Future模式的核心:去除了主函数的等待时间,并使得原本需要等待的时间段可以用于处理其他业务逻辑。
Futrure模式 :对于多线程,如果线程A要等待线程B的结果,那么线程A没必要等待B,直到B有结果,可以先拿到一个未来Future,等B有结果时再取真实的结果。
在多线程中经常举的一个例子 :网络图片的下载,刚开始是通过模糊的图片来代替最后的图片,等下载图片的线程下载完图片后在替换。而在这个过程中可以做一些其他的事情。
首先客户端向服务器请求RealSubject
,但是这个资源的创建是非常耗时的,怎么办呢?这种情况下,首先返回Client一个FutureSubject
,以满足客户端的需求,于此同时呢,Future会通过另外一个Thread 去构造一个真正的资源,资源准备完毕之后,在给Future一个通知。如果客户端急于获取这个真正的资源,那么就会阻塞客户端的其他所有线程,等待资源准备完毕。
示例代码(公共数据接口,FutureData和RealData都要实现):
package com.ylw.thread; public class FutureDemo2 { public static void main(String[] args) { FutureClient futureClient = new FutureClient(); Data request = futureClient.request("请求参数."); System.out.println("请求发送成功!"); System.out.println("执行其他任务..."); String result = request.getRequest(); System.out.println("获取到结果 -> " + result); } } /** * 公共数据接口,FutureData和RealData都要实现。 */ interface Data { public abstract String getRequest(); } /** * FutureData,当有线程想要获取RealData的时候,程序会被阻塞。 * <p> * 等到RealData被注入才会使用getReal()方法。 */ class FutureData implements Data { public volatile static boolean ISFLAG = false; private RealData realData; public synchronized void setRealData(RealData realData) { // 如果已经获取到结果,直接返回 if (ISFLAG) { return; } // 如果没有获取到数据,传递真是对象 this.realData = realData; ISFLAG = true; // 进行通知 notify(); } @Override public synchronized String getRequest() { while (!ISFLAG) { try { wait(); } catch (Exception e) { } } // 获取到数据,直接返回 return realData.getRequest(); } } /** * 真实数据RealData */ class RealData implements Data { private String result; public RealData(String data) { System.out.println("正在使用data:" + data + "网络请求数据,耗时操作需要等待."); try { Thread.sleep(3000); } catch (Exception e) { } System.out.println("操作完毕,获取结果..."); result = "666"; } @Override public String getRequest() { return result; } } /** * FutureClient 客户端 */ class FutureClient { public Data request(String queryStr) { FutureData futureData = new FutureData(); new Thread(new Runnable() { @Override public void run() { RealData realData = new RealData(queryStr); futureData.setRealData(realData); } }).start(); return futureData; } }
运行结果:
调用者请求资源 : client.request("name"); // 完成对数据的准备
当要获取资源的时候 : data.getResult()
,如果资源没有准备好isReady = false;那么就会阻塞该线程。直到资源获取然后该线程被唤醒。
总结