JDK、Guava异步回调使用及源码分析

使用JDK的FutureTask实现的异步调用l

Callable有返回值,和Runnable没有关系

public interface Callable<V> {
    V call() throws Exception;
}

public interface Runnable {
    public abstract void run();
}

FutureTask可以认为是一个Callable的适配器类,由于new Thread(Runnable)不能接收Callable,因此需要通过一个类进行适配——FutureTask,同时由于线程是没有返回值的,因此FutureTask需要有一个属性来储存Callable的返回值——outcome。

public class FutureTask<V> implements RunnableFuture<V> {

	/** 状态,通过该状态判断任务是否完成 */
    private volatile int state;
    private static final int NEW          = 0; // 初始化时为NEW
    private static final int COMPLETING   = 1; // 执行中
    private static final int NORMAL       = 2; // 可以理解为完成

	private Callable<V> callable;
	private Object outcome;

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        // 如果当前状态为未完成,则循环等待
        if (s <= COMPLETING) 
            s = awaitDone(false, 0L);
        return report(s); // 返回结果(outcome)
    }

	// 这里只列出了两行关键代码
    public void run() {
    	result = c.call(); // 调用callable的call()方法
    	set(result); // 见下面
    	// 简单点就是调用callable的call()方法,并
    }
    
    protected void set(V v) {
    	// 先将状态改为执行中
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v; // 将结果赋给outcome
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 将状态修改为NORMAL
            finishCompletion();
        }
    }
}

// 这里可以看到FutureTask继承了Future类
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

通过上面的代码,我们就知道Future的get()方法为什么是阻塞的,因为我们会循环判断Callable的call()方法是否执行完毕。
Future还支持有时间限制的等待,同样是阻塞的,这里不介绍了。

public V get(long timeout, TimeUnit unit)
下面通过一个示例演示JDK的Future实现的异步回调

创建一个Callable,计算1-10的和并返回,为了让阻塞更明显,这里让每次计算都等待0.3s

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableFutureDemo {

	 static class ComputeSumCallable implements Callable<Integer> {

		/**
		 * 计算和
		 */
		public Integer call() throws Exception {
			int sum = 0;
			for (int i = 1; i <= 10; i++) {
				Thread.sleep(300); // 每次计算等待0.3s
				sum += i;
			}
			return sum;
		}
	 }
	
	 public static void main(String[] args) throws InterruptedException, ExecutionException {
	 	// 1)创建Callable
		ComputeSumCallable computeSumCallable = new ComputeSumCallable();
		// 2)创建FutureTask(可以认为是Callable的适配器)
		FutureTask<Integer> futureTask = new FutureTask<Integer>(computeSumCallable);
		// 3)创建一个线程并开始任务
		new Thread(futureTask).start();
		// 4)阻塞的调用结果
		System.out.println("计算结果为:" + futureTask.get()); // 阻塞获取计算结果
	 }
}

也可以使用线程池直接执行Callable对象,会返回一个Future对象,使用instanceOf关键字可以看到,其实返回的Future对象是FutureTask的实例。(FutureTask继承了Future类,上面FutureTask源码最下面)

ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
Future<Integer> future = threadExecutor.submit(computeSumCallable);
System.out.println("计算结果为:" + future.get()); // 阻塞获取计算结果
Guava的异步回调
	<dependency>
	    <groupId>com.google.guava</groupId>
	    <artifactId>guava</artifactId>
	    <version>28.0-jre</version>
	</dependency>
接着对上面代码进行改写,使用Guava实现异步回调

这里可以先试着理解,然后再看源码,在最后把这段代码又贴了一遍。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

public class GuavaFutureDemo {

	 static class ComputeSumCallable implements Callable<Integer> {

		/**
		 * 计算和
		 */
		public Integer call() throws Exception {
			int sum = 0;
			for (int i = 1; i <= 10; i++) {
				Thread.sleep(300); // 每次计算等待0.3s
				sum += i;
			}
			return sum;
		}
	 }
	 
	 private static Integer sum = null;
	 
	 public static void main(String[] args) throws InterruptedException {
		 // 创建10个线程的线程池
		 ExecutorService jdkThreadPool = Executors.newFixedThreadPool(10);
		 // 创建Guava的线程池,作用就是将JDK线程池返回的Future改为自己的ListenableFuture
		 ListeningExecutorService guavaThreadPool = MoreExecutors.listeningDecorator(jdkThreadPool);
		 // 创建Callable
		ComputeSumCallable computeSumCallable = new ComputeSumCallable();
		// 执行任务
		ListenableFuture<Integer> computeListenableFuture = guavaThreadPool.submit(computeSumCallable);
		// 创建回调对象,任务执行完后根据结果进行调用
		FutureCallback<Integer> computeFutureCallback = new FutureCallback<Integer>() {
			public void onSuccess(Integer result) {
				sum = result;
			}
			public void onFailure(Throwable t) {
				System.err.println("计算过程中出现错误!");
				t.printStackTrace();
			}
		};
		// 为ListenableFuture添加回调对象(监听者)
		Futures.addCallback(computeListenableFuture, computeFutureCallback, guavaThreadPool);
		while (sum == null) {
			Thread.sleep(300);
			System.out.println("干点别的~~~");
		}
		System.out.println("计算结果:" + sum);
		guavaThreadPool.shutdown();
	}
}

执行结果:
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
计算结果:55

在此之前,先介绍一下JDK与Guava中Future的不同。之后将对这个不同进行详细解释。(请理解这两句话的含义,貌似不难。顺便结合上面的示例理解。)

JDK中的Future是我们交代给甲任务,我们等着甲做完,然后自己处理后事。
Guava中的Future是我们交代给甲任务,甲做完后,交给乙处理后事。

Guava对JDK的Future进行了扩展,增加了监听者。这里的监听者,就是上面说到的乙。

public interface ListenableFuture<V> extends Future<V> {
 	 void addListener(Runnable listener, Executor executor);
}

Guava中通过ExecutionList作为监听者列表,即一个Future可以有多个监听者,这些监听者通过链表形式存储。

public final class ExecutionList {
	// RunnableExecutorPair就是一个链表结构
	private RunnableExecutorPair runnables;
	// 是否执行过(在Callable执行后才开始执行,执行完后executed为true)
	private boolean executed;

	public void add(Runnable runnable, Executor executor) {
	    synchronized (this) {
	      if (!executed) {
	      	// 添加到监听者链表后面
	        runnables = new RunnableExecutorPair(runnable, executor, runnables);
	        return;
	      }
	    }
	    // 直接执行新添加的监听者方法
	    executeListener(runnable, executor);
	}

	public void execute() {
		// 循环链表,调用executeListener(),即直接调用线程池执行Runnable
	}
}

感觉上面解释的不是很清楚,这里用白话文解释一下:当Callable任务执行完毕,ListenableFuture调用内部ExecutionList对象的execute(),之后将executed设为true,如果这时我们再为ListenableFuture添加监听者,将直接调用线程池运行监听者的Runnable对象。(甲执行完了任务通知乙收尾)

上面我们知道了监听者是通过线程池执行的一个Runnable对象。上面说到,监听者是为了完成善后工作,也就是针对成功和失败进行不同的处理。

FutureCallback就是监听者最终要执行的任务。注意是最终,因为FutureCallback并不是一个Runnable类。也就是还要用Runnable对FutureCallback进行一层封装。这一层封装就是CallbackListener。

public interface FutureCallback<V> {
  /** 成功执行的函数 */
  void onSuccess(@Nullable V result);
  /** 异常失败执行的函数 */
  void onFailure(Throwable t);
}
private static final class CallbackListener<V> implements Runnable {
	final Future<V>	future;
	final FutureCallback<? super V> callback;

	CallbackListener( Future<V> future, FutureCallback<? super V> callback )
	{
		this.future = future;
		this.callback = callback;
	}

	// 如果成功则传递Future返回值,否则传递异常
	@Override
	public void run()
	{
		final V value;
		try {
			value = getDone(future); // 获取Future的返回值
		} catch ( ExecutionException e ) {
			callback.onFailure(e.getCause()); // 异常则调用失败处理
			return;
		} catch ( RuntimeException | Error e ) {
			callback.onFailure(e);  // 异常则调用失败处理
			return;
		}
		callback.onSuccess(value); // 否则调用成功处理
	}
}

现在梳理一下,ListenableFuture添加监听者,当Callable执行完后,通知监听者执行善后任务。ListenableFuture添加监听者以及监听者执行善后任务我们已经理清了,剩下ListenableFuture如何通知监听者。

这里需要用到在最上面我们介绍FutureTask,首先是run()被调用,然后调用set(),set()的最后调用了finishCompletion(),在finishCompletion()的最后执行了done();这个done()方法FutureTask并没有实现。

public class FutureTask<V> implements RunnableFuture<V> {

    public void run() {
    	result = c.call(); // 调用callable的call()方法
    	set(result); // 见下面
    	// 简单点就是调用callable的call()方法,并
    }
    
    protected void set(V v) {
    	// 先将状态改为执行中
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v; // 将结果赋给outcome
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 将状态修改为NORMAL
            finishCompletion();
        }
    }

    private void finishCompletion() {
    	// 中间无关代码直接省略了
        done();
    }

	protected void done() { }
}
public class ListenableFutureTask<V> extends FutureTask<V> implements ListenableFuture<V> {
	/** 监听者列表 */
	private final ExecutionList executionList = new ExecutionList();

	/** 添加监听者 */
    @Override
    public void addListener(Runnable listener, Executor exec) {
        executionList.add(listener, exec);
    }

	/** 调用监听者列表的execute()方法,该方法作用如果忘记可以回到上面看一下 */
    @Override
    protected void done() {
        executionList.execute();
    }
}
接着对上面代码进行改写,使用Guava实现异步回调
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

public class GuavaFutureDemo {

	 static class ComputeSumCallable implements Callable<Integer> {

		/**
		 * 计算和
		 */
		public Integer call() throws Exception {
			int sum = 0;
			for (int i = 1; i <= 10; i++) {
				Thread.sleep(300); // 每次计算等待0.3s
				sum += i;
			}
			return sum;
		}
	 }
	 
	 private static Integer sum = null;
	 
	 public static void main(String[] args) throws InterruptedException {
		 // 创建10个线程的线程池
		 ExecutorService jdkThreadPool = Executors.newFixedThreadPool(10);
		 // 创建Guava的线程池,作用就是将JDK线程池返回的Future改为自己的ListenableFuture
		 ListeningExecutorService guavaThreadPool = MoreExecutors.listeningDecorator(jdkThreadPool);
		 // 创建Callable
		ComputeSumCallable computeSumCallable = new ComputeSumCallable();
		// 执行任务
		ListenableFuture<Integer> computeListenableFuture = guavaThreadPool.submit(computeSumCallable);
		// 创建回调对象,任务执行完后根据结果进行调用
		FutureCallback<Integer> computeFutureCallback = new FutureCallback<Integer>() {
			public void onSuccess(Integer result) {
				sum = result;
			}
			public void onFailure(Throwable t) {
				System.err.println("计算过程中出现错误!");
				t.printStackTrace();
			}
		};
		// 为ListenableFuture添加回调对象(监听者)
		Futures.addCallback(computeListenableFuture, computeFutureCallback, guavaThreadPool);
		while (sum == null) {
			Thread.sleep(300);
			System.out.println("干点别的~~~");
		}
		System.out.println("计算结果:" + sum);
		guavaThreadPool.shutdown();
	}
}

执行结果:
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
干点别的~~~
计算结果:55

如果有误,欢迎指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值