随着微服务架构的兴起,跨设备调用越来越频繁,一个业务流程,可能调用N次第三方接口,获取N种上游数据。因此,如何高效率地异步去调取这些接口,然后同步的去处理这些接口返回的结果?这是高并发要解决的一个基础问题。
在Netty中,大量使用了异步回调模式。
A线程调用线程B的B.join方法,合并B线程。那么,线程A进入阻塞状态,知道B线程执行完成。
join方法的三个重载版本:
Callable接口:
FutrueTask:
Futrue接口:
public interface Future {
//取消并发任务的执行。
boolean cancel(booleanmayInterruptRunning);
booleanisCancelled();
//获取并发任务的执行状态。如果任务执行结束,则返回true。
booleanisCancelled():获取并发任务的取消状态。如果任务完成前被取消,则返回true。
booleanisDone();
//获取并发任务执行的结果。注意,这个方法是阻塞性的。如果并发任务没有执行完成,调用此方法的线程会一直阻塞,直到并发任务执行完成。
V get() throws InterruptedException, ExecutionException;
//获取并发任务执行的结果。也是阻塞性的,但是会有阻塞的时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。
V get(long timeout, TimeUnitunit) throws InterruptedException,
ExecutionException, TimeoutException;
}
FutrueTask深入:
本FutrueTask案例缺点:虽然用了Futrue模式,但是只是能获取异步结果,依然需要等待,依然是阻塞的。
public class JavaFutureDemo {
public static final int SLEEP_GAP = 500;
public static String getCurThreadName() {
return Thread.currentThread().getName();
}
//第一步,实现了Callable,替代Runnable,实现线程业务-烧水。
static class HotWarterJob implements Callable //①
{
@Override
public Boolean call() throws Exception //②
{
try {
Logger.info("洗好水壶");
Logger.info("灌上凉水");
Logger.info("放在火上");
//线程睡眠一段时间,代表烧水中
Thread.sleep(SLEEP_GAP);
Logger.info("水开了");
} catch (InterruptedException e) {
Logger.info(" 发生异常被中断.");
return false;
}
Logger.info(" 运行结束.");
return true;
}
}
//第二步,实现了Callable,替代Runnable,实现线程业务-清洗。
static class WashJob implements Callable {
@Override
public Boolean call() throws Exception {
try {
Logger.info("洗茶壶");
Logger.info("洗茶杯");
Logger.info("拿茶叶");
//线程睡眠一段时间,代表清洗中
Thread.sleep(SLEEP_GAP);
Logger.info("洗完了");
} catch (InterruptedException e) {
Logger.info(" 清洗工作发生异常被中断.");
return false;
}
Logger.info(" 清洗工作运行结束.");
return true;
}
}
public static void drinkTea(booleanwarterOk, booleancupOk) {
if (warterOk&&cupOk) {
Logger.info("泡茶喝");
} else if (! warterOk) {
Logger.info("烧水失败,没有茶喝了");
} else if (! cupOk) {
Logger.info("杯子洗不了,没有茶喝了");
}
}
public static void main(String args[]) {
//第三步:将callable封装成FutureTask
CallablehJob = new HotWarterJob(); //③
//第四步:将FutureTask装入线程
FutureTaskhTask = new FutureTask<>(hJob); //④
Thread hThread = new Thread(hTask, "** 烧水-Thread"); //⑤
CallablewJob = new WashJob(); //③
FutureTaskwTask = new FutureTask<>(wJob); //④
Thread wThread = new Thread(wTask, "$$ 清洗-Thread"); //⑤
//第五步:启动线程
hThread.start();
wThread.start();
Thread.currentThread().setName("主线程");
try {
//第六步:获取线程结果
boolean warterOk = hTask.get();
boolean cupOk = wTask.get();
//第七步:线程结果处理
drinkTea(warterOk, cupOk);
} catch (InterruptedException e) {
Logger.info(getCurThreadName() + "发生异常被中断.");
} catch (ExecutionException e) {
e.printStackTrace();
}
Logger.info(getCurThreadName() + " 运行结束.");
}
}
谷歌高并发包,对java的异步回调机制做了增强:
public interface ListenableFuture extends Future {
//此方法由Guava内部调用
//将FutureCallback回调工作,封装成一个内部的runnable异步回调任务,在Callable异步任务完成后,回调FutureCallback。
//白话说,FutureCallback中的success和fail在这里封装成runnable送入线程池被执行。
void addListener(Runnable r, Executor e);
}
public interface FutureCallback {
void onSuccess(@Nullable V var1);
void onFailure(Throwable var1);
}
简单实例
Futures.addCallback(listenableFuture, newFutureCallback()
{
public void onSuccess(Boolean r)
{
// listenableFuture内部的Callable成功时回调此方法
}
public void onFailure(Throwable t)
{
// listenableFuture内部的Callable异常时回调此方法
}
});
异步回调案例
Guava案例
//….
public class GuavaFutureDemo {
public static final int SLEEP_GAP = 500;
public static String getCurThreadName() {
return Thread.currentThread().getName();
}
//业务逻辑:烧水
static class HotWarterJob implements Callable
{
@Override
public Boolean call() throws Exception
{
//……省略,与使用FutureTask实现异步泡茶喝相同
}
}
//业务逻辑:清洗
static class WashJob implements Callable {
@Override
public Boolean call() throws Exception {
//……省略,与使用FutureTask实现异步泡茶喝相同
}
}
//新创建一个异步业务类型,作为泡茶喝主线程类
static class MainJob implements Runnable {
booleanwarterOk = false;
booleancupOk = false;
int gap = SLEEP_GAP / 10;
@Override
public void run() {
while (true) {
try { Thread.sleep(gap);
Logger.info("读书中......");
} catch (InterruptedException e) {
Logger.info(getCurThreadName() + "发生异常被中断.");
}
if (warterOk&&cupOk) {
drinkTea(warterOk, cupOk);
}
}
}
public void drinkTea(Boolean wOk, Boolean cOK) {
if (wOk&&cOK) {
Logger.info("泡茶喝,茶喝完");
this.warterOk = false;
this.gap = SLEEP_GAP * 100;
} else if (! wOk) {
Logger.info("烧水失败,没有茶喝了");
} else if (! cOK) {
Logger.info("杯子洗不了,没有茶喝了");
}
}
}
public static void main(String args[]) {
//第一步:启动主线程。
//创建一个新的线程实例,作为泡茶主线程
MainJobmainJob = new MainJob();
Thread mainThread = new Thread(mainJob);
mainThread.setName("主线程");
mainThread.start();
//烧水的业务逻辑实例
CallablehotJob = new HotWarterJob();
//清洗的业务逻辑实例
CallablewashJob = new WashJob();
//第二步:创建子线程池
//创建Java线程池
ExecutorService jPool = Executors.newFixedThreadPool(10);
//包装Java线程池,构造Guava线程池
ListeningExecutorService gPool = MoreExecutors.listeningDecorator(jPool);
//第三步:线程池中注入任务,并获取结果
//提交烧水的业务逻辑实例,到Guava线程池获取异步任务
ListenableFuture hotFuture = gPool.submit(hotJob);
//第四步:设置回调。
//绑定异步回调,烧水完成后,把喝水任务的warterOk标志设置为true
Futures.addCallback(hotFuture, new FutureCallback() {
public void onSuccess(Boolean r) {
if (r) {
mainJob.warterOk = true;
}
}
public void onFailure(Throwable t) {
Logger.info("烧水失败,没有茶喝了");
}
};
//提交清洗的业务逻辑实例,到Guava线程池获取异步任务
ListenableFuture washFuture = gPool.submit(washJob);
//绑定任务执行完成后的回调逻辑到异步任务
Futures.addCallback(washFuture, new FutureCallback() {
public void onSuccess(Boolean r) {
if (r) {
mainJob.cupOk = true;
}
}
public void onFailure(Throwable t) {
Logger.info("杯子洗不了,没有茶喝了");
}
});
}
}
netty和guava一样,实现了自己的异步回调机体系。netty集成和扩展了JDK Future系列异步回调的API,定义了自身的Future系列接口和类。实现了异步任务的监控、异步执行结果的获取。
netty与guava的模式类比:
public interface Future extendsjava.util.concurrent.Future {
booleanisSuccess(); // 判断异步执行是否成功
booleanisCancellable(); // 判断异步执行是否取消
Throwable cause(); //获取异步任务异常的原因
//增加异步任务执行完成与否的监听器Listener
FutureaddListener(GenericFutureListener extends Future super V>>listener);
//移除异步任务执行完成与否的监听器Listener
FutureremoveListener(GenericFutureListener extends Future superV>> listener);
//....
}
一般netty有一系列的子类,实际使用。
在netty 网络连接中,网络连接通道的输入和输出处理都是异步的。都会返回一个ChannelFuture接口的实例。
//connect是异步的,仅提交异步任务
ChannelFuture future= bootstrap.connect(new InetSocketAddress("www.manning.com",80));
//connect的异步任务真正执行完成后,future回调监听器才会执行
future.addListener(new ChannelFutureListener(){
@Override
public void operationComplete(ChannelFuture channelFuture)
throws Exception {
if(channelFuture.isSuccess()){
System.out.println("Connection established");
} else {
System.err.println("Connection attempt failed");
channelFuture.cause().printStackTrace();
}
}
});
//GenericFutureListener的父接口EventListener是一个空接口,没有任何的抽象方法,是一个仅仅具有标识作用的接口。
public interface GenericFutureListener> extends EventListener {
//监听器的回调方法
void operationComplete(F var1) throws Exception;
}