java并发编程Future类详解

作用和举例

在这里插入图片描述
future类的作用就是为了调用其他线程完成好后的结果,再返回到当前线程中,如上图举例:

小王自己是主线程,叫外卖等于使用future类,叫好外卖后小王就接着干自己的事去了,当外卖到了的时候,future.get获取,继续做接下来的事情

但要注意的是当还没获取外卖的时候,主线程中用餐这一步是卡住的

另一个实际项目中的例子:
在进行传统的 RPC(远程调用)时,同步调用 RPC 是一段耗时的过程。当客户端发出 RPC请求,服务端完成请求处理需要很长的一段时间才会返回,这个过程中客户端一直在等待,直到 数据返回随后再进行其他任务的处理。现有一个 Client 同步对三个 Server 分别进行一次 RPC调用。

在这里插入图片描述

假设一次远程调用的时间为 500ms,则一个 Client 同步对三个 Server 分别进行一次 RPC 调 用的总时间,需要耗费 1500ms。如果节省这个总时间呢,可以使用 Future 模式对其进行改造,将同步的 RPC 调用改为异步并发的 RPC 调用,一个 Client 异步并发对三个 Server 分别进行一次 RPC 调用

在这里插入图片描述

JDK中的Future实现

结构如下
在这里插入图片描述
future接口定义的方法:

  • cancel():如果等太久,你可以直接取消这个任务
  • isCancelled():任务是不是已经取消了
  • isDone():任务是不是已经完成了
  • get():有2个get()方法,不带参数的表示无穷等待,或者你可以只等待给定时间

使用:

	//异步操作 可以用一个线程池
	ExecutorService executor = Executors.newFixedThreadPool(1);
	//执行FutureTask,相当于上例中的 client.request("name") 发送请求
	//在这里开启线程进行RealData的call()执行
	Future<String> future = executor.submit(new RealData("name"));
	System.out.println("请求完毕,数据准备中");
	try {
	     //这里依然可以做额外的数据操作,这里使用sleep代替其他业务逻辑的处理
	    Thread.sleep(2000);
	} catch (InterruptedException e) {
	}
	//如果此时call()方法没有执行完成,则依然会等待
	System.out.println("数据 = " + future.get());

executor.submit()里传线程类
在这里插入图片描述
走这里看源码
在这里插入图片描述

	public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
	    // 根据Callable对象,创建一个RunnableFuture,这里其实就是FutureTask
	    RunnableFuture<T> ftask = newTaskFor(task);
	    //将ftask推送到线程池
	    //在新线程中执行的,就是run()方法,在下面的代码中有给出
	    execute(ftask);
	    //返回这个Future,将来通过这个Future就可以得到执行的结果
	    return ftask;
    }
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

RunnableFuture其实就是FutureTask
在这里插入图片描述

FutureTask作为一个线程单独执行时,会将结果保存到outcome中,并设置任务的状态,下面是FutureTask的run()方法:
在这里插入图片描述
Future类最关键的get实现
在这里插入图片描述

FutureTask源码,关键在于awaitDone()方法里有park()阻塞了线程,成功后返回outcome
在这里插入图片描述
outcome:保存的就是最终的计算结果,get/set里都有它
在这里插入图片描述
在这里插入图片描述

Future模式的高阶版本—— CompletableFuture

Future模式虽然好用,但也有一个问题,那就是将任务提交给线程后,调用线程并不知道这个任务什么时候执行完,如果执行调用get()方法或者isDone()方法判断,可能会进行不必要的等待,那么系统的吞吐量很难提高。

CompletableFuture可以理解为Future模式的升级版本,它最大的作用是提供了一个回调机制,可以在任务完成后,自动回调一些后续的处理,这样,整个程序可以把“结果等待”完全给移除了。

例子:
在这里插入图片描述

它可以在Future执行成功后,自动回调进行下一步的操作,因此整个程序不会有任何阻塞的地方(也就是说你不用去到处等待Future的执行,而是让Future执行成功后,自动来告诉你)。

CompletableFuture之所有会有那么神奇的功能,完全得益于AsyncSupply类(由上述代码中的supplyAsync()方法创建)
在这里插入图片描述
在这里插入图片描述

作为Runnable,看他run的方法
在这里插入图片描述
d.completeValue(f.get())就是你要执行的异步方法,结果会被保存下来,放到d.result字段中

d.postComplete()会调用后续一系列操作,在这个后续处理中,就会调用thenAccept()中的消费者,相当于Future完成后的通知

代码如下:

   final void postComplete() {
                //省略部分代码,重点在tryFire()里
                //在tryFire()里,真正触发了后续的调用,也就是thenAccept()中的部分
                f = (d = h.tryFire(NESTED)) == null ? this : d;
            }
        }
    }

实现自己的Future模式

还是用敖丙的例子,主要实现类如下:
在这里插入图片描述

先定义接口

public interface Data {
    public String getResult ();
}

实现自己的future类:

public class FutureData implements Data {
    // 内部需要维护RealData
    protected RealData realdata = null;          
    protected boolean isReady = false;
    public synchronized void setRealData(RealData realdata) {
        if (isReady) { 
            return;
        }
        this.realdata = realdata;
        isReady = true;
        //RealData已经被注入,通知getResult()
        notifyAll();                            			
    }
    //会等待RealData构造完成
    public synchronized String getResult() {        	
        while (!isReady) {
            try {
                //一直等待,直到RealData被注入
                wait();                        			
            } catch (InterruptedException e) {
            }
        }
        //真正需要的数据从RealData获取
        return realdata.result;                    		
    }
}

关键在于isReady这个布尔,while (!isReady)作为自旋锁,想要获取这个future的结果,他会一直等待,直到RealData被注入

RealData类:

public class RealData implements Data {
    protected final String result;
    public RealData(String para) {
        StringBuffer sb=new StringBuffer();
        //假设这里很慢很慢,构造RealData不是一个容易的事
        result =sb.toString();
    }
    public String getResult() {
        return result;
    }
}

Client类,目的是将RealData注入到FutureData

public class Client {
    //这是一个异步方法,返回的Data接口是一个Future
    public Data request(final String queryStr) {
        final FutureData future = new FutureData();
        new Thread() {                                      
            public void run() {                    	
                // RealData的构建很慢,所以在单独的线程中进行
                RealData realdata = new RealData(queryStr);
                //setRealData()的时候会notify()等待在这个future上的对象
                future.setRealData(realdata);
            }                                               
        }.start();
        // FutureData会被立即返回,不会等待RealData被构造完
        return future;                        		
    }
}

mian方法通过data.getResult()拿数据

public static void main(String[] args) {
    Client client = new Client();
    //这里会立即返回,因为得到的是FutureData而不是RealData
    Data data = client.request("name");
    System.out.println("请求完毕");
    try {
        //这里可以用一个sleep代替了对其他业务逻辑的处理
        //在处理这些业务逻辑的过程中,RealData被创建,从而充分利用了等待时间
        Thread.sleep(2000);
    } catch (InterruptedException e) {
    }
    //使用真实的数据,如果到这里数据还没有准备好,getResult()会等待数据准备完,再返回
    System.out.println("数据 = " + data.getResult());
}

自己实现就拿wait简单处理,future类使用park阻塞

总结

Future 模式的核心思想是异步调用,有点类似于异步的 Ajax 请求。当调用某个耗时方法时, 可以不急于立刻获取结果,可以让被调用者立刻返回一个契约(或异步任务), 并且将耗时的方法放到另外线程执行,后续凭契约再去获取异步执行的结果。

在具体的实现上,Future 模式和异步回调模式既有区别,又有联系。Java 的 Future 模式实现,没有实现异步回调模式,仍然需要主动去获取耗时任务的结果;而 Java 8 中的 CompletableFuture,实现了异步回调模式。

参考:
JAVA Future类详解

更详细的可以看这一遍
Future模式与异步回调模式

  • 16
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值