Java异步编程
经典泡茶
从经典的泡茶案例说起,洗茶壶、洗茶具和烧开水被认定为比较耗时的操作,在下面的程序中假定洗茶壶需要2秒、洗茶具需要3秒、烧开水需要5秒(请忽略下图比例问题)通过jdk或第三方jar包中的类与接口来实现“异步泡茶”。
异步阻塞join
join()
是线程类中的一个实例方法,它会阻塞当前执行该方法的线程,待目标线程(即调用该方法的线程对象所对应的线程,说起来有些拗口)执行完毕后再执行。下面的代码共有三个线程,主线程、烧水线程及清洗线程,主线程负责触发烧水线程和清洗线程启动,烧水线程和清洗线程并发的执行,待其执行完毕后主线程即可泡茶喝。
public class JoinDemo {
public static void main(String[] args) {
long begin = System.currentTimeMillis();
Thread t1 = new Thread(new wash(), "清洗线程");
Thread t2 = new Thread(new water(), "烧水线程");
t1.start();
t2.start();
try {
t1.join();
t2.join();
System.out.println("泡茶喝");
System.out.println(System.currentTimeMillis() - begin); //5018
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//清洗线程
static class wash implements Runnable {
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "开始洗茶壶");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "开始洗茶杯");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "都洗好了!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//烧水线程
static class water implements Runnable {
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "开始烧水");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "水开了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
异步阻塞Future
Java中有一个Callable接口,和Runnable接口一样Callable接口也是提供一个可执行的任务给线程执行,Runnable接口使用的最大不便是它的run方法没有返回值以及无法抛出异常,Callable接口更为强大一点,它的call方法可以抛出异常并且是有返回值的。但是Callable接口的实现类对象并不能像Runnable接口的对象一样直接送到线程的构造方法中,它需要借助FutureTask类将自己“包装成一个Runnable”送进构造方法中,FutureTask类实现了Runnable和Future两个接口,前者让它可以作为Runnable实现类直接被送进线程Thread的构造方法中,后者为它提供了一系列把握任务生命周期的方法。
public class FutureDemo {
public static void main(String[] args) {
//使用FutureTask包装一下
FutureTask<Boolean> washTarget = new FutureTask<Boolean>(new wash());
FutureTask<Boolean> waterTarget = new FutureTask<Boolean>(new water());
Thread t1 = new Thread(washTarget, "清洗线程--");
Thread t2 = new Thread(waterTarget, "烧水线程--");
t1.start();
t2.start();
try {
//获取任务执行结果
Boolean washBool = washTarget.get();
Boolean waterBool = waterTarget.get();
if (washBool && waterBool){ //全部成功再泡茶
System.out.println("泡茶喝");
}
} catch (Exception e) {
e.printStackTrace();
}
}
static class wash implements Callable<Boolean>{
public Boolean call() throws Exception {
try {
System.out.println(Thread.currentThread().getName()+"开始洗茶壶");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"开始洗茶杯");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"都洗好了!");
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
return true;
}
}
static class water implements Callable<Boolean>{
public Boolean call() throws Exception {
try {
System.out.println(Thread.currentThread().getName()+"开始烧水");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+"水开了");
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
return true;
}
}
}
Callable是一个泛型接口,其call方法返回值是其泛型类型,call方法的返回值会保存在FutureTask类中的一个成员变量outcome
中,并可以通过get()
方法获取执行结果。值得注意的是:get()
方法被调用后同样会阻塞当前线程直至任务执行完毕,届时get方法会返回执行结果或抛出一个异常。
上面的代码中只是用了get方法,这是最常用的方法,但Future接口提供的方法还有很多。
异步回调Guava
<dependency>
<groupId>com.vaadin.external.google</groupId>
<artifactId>guava</artifactId>
<version>16.0.1.vaadin1</version>
</dependency>
Guava是谷歌提供的一个Java工具包,使用Guava实现异步非阻塞的“泡茶”主要需要借助ListenableFuture
和FutureCallback
两个接口。ListenableFuture
扩展了Future接口,它新增了一个addListener(Runnable r,Executor e)
方法,这个方法可以为任务绑定一个监听器用于执行回调函数,但实际编程中通常使用Futures
工具类中的静态方法addCallback(ListenableFuture<V> future, FutureCallback<? super V> callback)
方法完成任务和回调函数的绑定。FutureCallback
接口即为上述提到的回调函数,需要重写其中的void onSuccess(V var1)
和void onFailure(Throwable var1)
两个方法,前者在任务顺利执行完毕后被调用,入参是任务执行结果;后者在任务抛出异常后被调用,入参是所抛出的异常。
public class GuavaDemo {
//两个标志
static boolean warterOk = false;
static boolean cupOk = false;
public static void main(String[] args) {
Callable<Boolean> wash = new Wash();
Callable<Boolean> water = new Water();
final ExecutorService jPool = Executors.newFixedThreadPool(10);
ListeningExecutorService gPool = MoreExecutors.listeningDecorator(jPool);
ListenableFuture<Boolean> washFuture = gPool.submit(wash);
ListenableFuture<Boolean> waterFuture = gPool.submit(water);
//回调函数
Futures.addCallback(washFuture, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean aBoolean) {
if (aBoolean) {
cupOk = true;
}
}
@Override
public void onFailure(Throwable throwable) {
System.out.println("清洗失败");
}
});
Futures.addCallback(waterFuture, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean aBoolean) {
if (aBoolean)
warterOk = true;
}
@Override
public void onFailure(Throwable throwable) {
System.out.println("烧水失败!");
}
});
//
while (true) {
try {
Thread.sleep(1000);
System.out.println("看书呢。。。");
if (warterOk && cupOk) {
System.out.println("泡茶喝。。。");
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
jPool.shutdown();
}
static class Wash implements Callable<Boolean> {
public Boolean call() throws Exception {
try {
System.out.println(Thread.currentThread().getName() + "开始洗茶壶");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "开始洗茶杯");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "都洗好了!");
} catch (InterruptedException e) {
return false;
}
return true;
}
}
static class Water implements Callable<Boolean> {
public Boolean call() throws Exception {
try {
System.out.println(Thread.currentThread().getName() + "开始烧水");
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "水开了");
} catch (InterruptedException e) {
return false;
}
return true;
}
}
}
上面的代码中加入了一个新的行为“看书”为了体现出这是一个非阻塞的操作,同时新增了两个布尔成员,在回调函数中通过改变它们的值为true来告知主线程清洗或烧水已经成功。
参考书籍
尼恩《Netty、Redis、Zookeeper高并发实战》