说一说并发设计模式---Future(异步)

3,028 阅读6分钟

网上讲解该设计模式的文章非常地多,很多讲的内容、技术都比我全面和深入!但既然学习,我还是想进行一下总结!希望阅读本文章的你可以获得一点启发!

说到并发编程,我们都会觉得,哇,并发编程是真的难!可事实真的是那样子吗?我觉得不是,一门技术出来,只要你有那个决心去攻克它,那么你就赢了。也就是我们常说的:“稳住,猥琐发育,别浪,咱能赢!”,学习的过程中肯定会遇到一些难点,多找人沟通交流,相互进步是非常好的途径,小编希望你们在阅读我总结的文章之后能和小编一起交流,不甚荣幸。

一、什么是异步?

讲解设计模式之前我先讲下异步,异步和同步是对立的,这两个概念其实是不难理解的,关键是同步和异步究竟是怎样在程序中体现,才是我们最好奇,最想知道的。

同步:就是当任务A依赖于任务B的执行时,必须等待任务B执行完毕之后任务A才继续执行,此过程任务A被阻塞。任务要么都成功,要么都失败!想一想我们打电话的情景即可! 异步:任务A调用任务B,任务A不需要等到任务B执行完毕,任务B只是返回一个虚拟的结果给任务A,使得任务A能够继续做其他事情,等到任务B执行完成之后再通知任务A(回调)或者是任务A主动去请求任务B要结果。想一想发短信的情景即可!

二、Future异步设计模式

首先我在网上找到了一张图,分别对比了异步同步的时序区别:

那么我们怎么实现呢?先贴一张UML图再说:

讲解实例之前,我觉得有必要说一些先决条件:

  • 1、notify/wait的使用,每个java对象都拥有这两个方法,调用这两个方法必须先获得监视器,常用的做法是在synchronized同步块中进行调用
  • 2、生产者/消费者模型,其实这个可有可无,主要是不要写出在if(块中)调用notify/wait方法,而要改为while

我所有的逻辑注释都穿插在下面的代码中,如果有想要一起交流的问题,欢迎评论,欢迎评论,欢迎评论!

Main.java

package com.wokao66.future;

/**
 * 持有一个客户端Client对象,发送请求
 * @author: huangjiawei
 * @since: 2018年4月2日
 * @version: $Revision$ $Date$ $LastChangedBy$
 */
public class Main {
	public static void main(String[] args) throws Exception {
		/**
		 * 持有一个客户端Client对象	
		 */
		Client client = new Client();
		/**
		 * 返回一个虚拟的数据(这是异步返回的,虚拟的,不是真实的,但必须持有真实数据对象realData,方便后面获取请求结果)
		 */
		Data virtualData = client.request("我要下单!!!!");
		/**
		 * 睡眠5秒
		 */
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {}
		/**
		 * 我现在想获得真实数据了
		 */
		String realData = virtualData.getResultData();
		System.err.println("真实数据为:" + realData);
	}
}

Data.java

package com.wokao66.future;

/**
 * 对返回数据的简单抽象(虚拟数据和真实数据都必须实现该接口)
 * @author: huangjiawei
 * @since: 2018年4月2日
 * @version: $Revision$ $Date$ $LastChangedBy$
 */
public interface Data {

	/**
	 * 获取数据的操作,至于是虚拟的,还是真实的,我不用管,让实现类去决定
	 * @throws Exception 
	 */
	public abstract String getResultData() throws Exception;
}

VirtualData.java

package com.wokao66.future;

/**
 * 虚拟的数据(异步返回给客户端)
 * @author: huangjiawei
 * @since: 2018年4月2日
 * @version: $Revision$ $Date$ $LastChangedBy$
 */
public class VirtualData implements Data {

	/**
	 * 想象一下,如果你这里返回的VirtualData不包含对RealData的引用,那么当客户端需要获取真实数据时,你的数据从何而来???
	 */
	private RealData realData = null;
	/**
	 * 默认是还没有准备好数据嘛!要有个状态来跟踪
	 */
	private boolean isReady = false;

	/**
	 * 注入RealData,这个RealData
	 * @param realData
	 */
	public synchronized void setRealData(RealData realData) {
		System.err.println("获得锁");
		/**
		 * 如果还没有准备好,我就需要
		 */
		if (isReady) {
			return;
		}
		this.realData = realData;
		isReady = true;//我已经准备好了
		notify();//通知所有阻塞的线程
	}

	/**
	 * 重写获取数据的方式
	 * @throws Exception 
	 */
	@Override
	public synchronized String getResultData() throws Exception {
		/**
		 * 如果客户端调用的时候我还没有注入真实数据,那么就一直阻塞
		 */
		while (!isReady) {
			//调用wait必须先获得对象的锁,所以
			wait();
		}
		return realData.getResultData();

	}
}

RealData.java

package com.wokao66.future;

/**
 * 这个类表示你具体的业务操作,比如重数据库查询数据
 * @author: huangjiawei
 * @since: 2018年4月2日
 * @version: $Revision$ $Date$ $LastChangedBy$
 */
public class RealData implements Data {
	/**
	 * 请求名
	 */
	private String readData;
	public RealData(String readData) {
		/**
		 * 我这里先休眠10秒,表示一个耗时的操作
		 */
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {}

		this.readData = "调用名为 : " + readData + " , " + "真实的数据为 : realData";
	}
	@Override
	public String getResultData() {
		return readData;
	}
}

Client.java

package com.wokao66.future;

/**
 * 表示我们的客户端程序嘛!负责发起调用请求
 * @author: huangjiawei
 * @since: 2018年4月2日
 * @version: $Revision$ $Date$ $LastChangedBy$
 */
public class Client {

	/**
	 * 表示客户端的请求
	 * @param name 具体的请求名称
	 * @return
	 */
	public Data request(String name) {
		/**
		 * 声明一个虚拟的数据
		 */
		VirtualData virtualData = new VirtualData();
		/**
		 * 当你调用请求时,我后台默默开启一个线程去处理真实操作
		 */
		new Thread(new Runnable() {

			@Override
			public void run() {
				System.err.println("我偷偷摸摸地请求后台获取数据的操作,该操作可能会执行很长的时间");
				System.err.println("我不管了,先返回结果给调用方");
				RealData realData = new RealData(name);
				//下面两句的执行顺序是不一样的
				//启动线程
				virtualData.setRealData(realData);
			}
		}).start();
		//我先返回一个虚拟的数据给你,真的数据等我获取完成之后你再过来取
		return virtualData;
	}
}

运行结果

下单成功...............
我偷偷摸摸地请求后台获取数据的操作,该操作可能会执行很长的时间
我不管了,先返回结果给调用方
获得锁
真实数据为:调用名为 : 我要下单!!!! , 真实的数据为 : realData

不知道大家有没有思考过,上面Main.javaString realData = virtualData.getResultData();是一个本地对象,只能在本机执行,那如果是基于tcp的网络传输呢?可以采用个推,websocket等方式进行,dubbo目测也是可以的!对了,突然想起来,Java EE 7开始已经支持异步Servlet了,详细的可以自行查看相关文档!

三、Java 中的异步并发编程

异步执行结果无非就有两种形式:

  • 1、执行之后有回调
  • 2、执行之后没有回调

有回调指的是客户端建立相应的监听器Listener,服务端执行异步Future之后主动回调客户端的回调函数

无回调即我们前面例子讲的,我们需要自己主动去请求服务端判断是否已经成功了

package com.wokao66.javafuture;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test {

	public static void main(String[] args) {
		/**
		 * 新建一个线程池
		 */
		ExecutorService executor = Executors.newFixedThreadPool(1);
		/**
		 * 创建一个任务
		 */
		Task task = new Task();
		/**
		 * 将任务提交给线程池
		 */
		Future<Integer> result = executor.submit(task);
		//这里会保证所有子线程执行完毕再关闭
		executor.shutdown();

		try {
			/**
			 * 模拟执行其它操作
			 */
			Thread.sleep(1000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		System.out.println("我在执行其他操作........");

		try {
			System.out.println("任务的执行结果是:" + result.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}

	}
}

class Task implements Callable<Integer> {

	@Override
	public Integer call() throws Exception {
		System.out.println("子线程在进行计算");
		/**
		 * 模拟执行耗时操作
		 */
		Thread.sleep(10000);

		return 100;
	}
}

执行结果

子线程在进行计算
我在执行其他操作........
任务的执行结果是:100

同时java也可以使用FutureTask来创建任务,FutureTask同时实现RunnableCallable接口,非常方便!

谢谢阅读!