整个 Netty 的 API 都是异步的,异步处理不是一个新的机制,这个机制出来已经有一些时间了。对网络应用来说,IO 一般是性能的瓶颈,使用异步 IO 可以较大程度上高程序性能,因为异步变的越来越重要。但是它是如何工作的呢?以及有哪些不同的模式可用呢?
异步处理倡更有效的使用资源,它允许你创建一个任务,当有事件发生时将获得通知并等待事件完成。这样就不会阻塞,不管事件完成与否都会及时返回,资源利用率更高,程序可以利用剩余的资源做一些其他的事情。
本节将说明一起工作或实现异步 API 的两个最常用的方法,并讨论这些技术之间的差异
一、Callback(回调)
回调是异步处理的一种技术。一个回调是被传递到并且执行完该方法。
下面代码是一个简单的回调:
public class Worker {
public void doWork() {
Fetcher fetcher = new MyFetcher(new Data(1, 0));
fetcher.fetchData(new FetcherCallback() {
@Override
public void onData(Data data) throws Exception {
System.out.println("Data received : " + data);
}
@Override
public void onError(Throwable cause) {
System.out.println("An err occured : " + cause.getMessage()); }
});
}
public static void main(String[] args) {
Worker w = new Worker();
w.doWork();
}
}
public interface Fetcher {
void fetchData(FetcherCallback callback);
}
public class MyFetcher implements Fetcher {
final Data data;
public MyFetcher(Data data) {
this.data = data;
}
@Override
public void fetchData(FetcherCallback callback) {
try {
callback.onData(data);
} catch (Exception e) {
callback.onError(e);
}
}
}
public interface FetcherCallback {
void onData(Data data) throws Exception;
void onError(Throwable cause);
}
public class Data {
private int m;
private int n;
public Data(int m, int n) {
this.m = m;
this.n = n;
}
@Override
public String toString() {
int r = n / m;
return n + "/" + m + " = " + r;
}
}
上面的例子只是一个简单的模拟回调,要明白其所表达的含义。Fetcher.fetchData()方法需传递一个FetcherCallback 类型的参数,当获得数据或发生错误时被回调。对于每种情况都供了同意的方法:
FetcherCallback.onData(),将接收数据时被调用
FetcherCallback.onError(),发生错误时被调用
因为可以将这些方法的执行从"caller"线程移动到其他的线程执行;但也不会保证 FetcherCallback 的每个方法都会被执行。回调过程有个问题就是当你使用链式调用很多不同的方法会导致线性代码;有些人认为这种链式调用方法会导致代码难以阅读,但是我认为这是一种风格和习惯问题。例如,基于 Javascript 的 Node.js 越来越受欢迎,它使用了大量的回调,许多人都认为它的这种方式利于阅读和编写。
二、Futures
第二种技术是使用 Futures。Futures 是一个抽象的概念,它表示一个值,该值可能在某一点变得可用。一个 Future 要么获得计算完的结果,要么获得计算失败后的异常。Java 在 java.util.concurrent 包中附带了 Future 接口,它使用 Executor 异步执行。例如下面的代码,每传递一个 Runnable 对象到 ExecutorService.submit()方法就会得到一个回调的 Future,你能使用它检测是否执行完成。
public class FutureExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
Runnable task1 = new Runnable() {
@Override
public void run() {
// do something
System.out.println("i am task1...");
}
};
Callable task2 = new Callable() {
@Override
public Integer call() throws Exception {
// do something
return new Integer(100);
}
};
Future> f1 = executor.submit(task1);
Future f2 = executor.submit(task2);
System.out.println("task1 is done ? " + f1.isDone());
System.out.println("task2 is done ? " + f2.isDone());
// waiting task1 done
while (f1.isDone()) {
System.out.println("task1 done");
break;
}
// waiting task2 done
while (f2.isDone()) {
System.out.println("task2 done");
break;
}
}
}
有时候使用 Future 感觉很丑陋,因为你需要间隔检查 Future 是否已完成,而使用回调会直接收到返回通知。看完这两个常用的异步执行技术后,你可能想知道使用哪个最好?这里没有明确的答案。事实上,Netty 两者都使用,供两全其美的方案。