为啥会有Future
常见的开启线程方式有两种:
- 重写Thread的run方法,调用start开启线程。
- 给Thread传参Runnable实现类对象(需要实现run方法),调用start开启线程。
但是这两种方式都有局限性,那就是run方法虽然运行在子线程中,但是我们无法得知run方法执行的结果。这时又出现了几个相关类Future、FutureTask、Callable。这几个类结合Thread就可实现获取其他线程中执行方法后的返回结果。
相关简介
Future相关主要有两个类FutureTask和Callable其中Future是一个接口FutureTask是其实现类,Callable类似Runnable。这部分就谈谈如何使用以及API介绍。
首先康康怎样用
首先先来上手写法,有如下三步:
1、创建Callable实现类,实现call接口。
2、创建FutureTask实例,传参callable。
3、借助Thread执行,吧Runnable实现类传参给Thread。
// 1、创建Callable实现类,实现call接口。
val callable = object : Callable<Int> {
override fun call(): Int {
// 运行在子线程中
val a = 1
val b = 2
// 模拟耗时操作
SystemClock.sleep(1000)
return a + b
}
}
//2、创建FutureTask实例,传参callable。(其实FutureTask 就是Runnable的实现类)
val futureTask = FutureTask(callable)
// 3、借助Thread执行,Runnable实现类传参给Thread。
Thread(futureTask).start()
// 其他线程中可阻塞等待获取结果
val result = futureTask.get()
println("result:$result") // System.out: result:3
当熟悉了上面就可以这样变换了:
// Callable 直接以Lambda 作为FutureTask构造参数。
val futureTask = FutureTask {
val a = 1
val b = 2
SystemClock.sleep(1000)
a + b
}
Thread(futureTask).start()
val result = futureTask.get()
1、看过上述的用法后我们不难发现,这个和普通开启多线程方式十分类似。二者是通过Thread构造传参Runnable 然后调用start的方式开启子线程。只是Future这里有一点区别,这里需要使用FutureTask这个Runnable实现类,这个类为系统定义好的。
2、创建FutureTask时再提供一个Callable即可,这里就是要在子线程中执行的任务。
接下来康康相关API
Future接口很简单,定义在并发包中的一个类。定义了子类需要实现的接口。
package java.util.concurrent
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
/*
等待计算结果完成,获取计算结果。会抛出如下异常:
1、CancellationException
2、ExecutionException
3、InterruptedException
*/
V get() throws InterruptedException, ExecutionException;
/*
获取执行的结果,最长等待时间为timeout。
可能会抛出以下异常:
1、CancellationException:当计算被取消
2、ExecutionException:当计算过程中threw an exception
3、InterruptedException:当get等待结果时线程被打断。
4、TimeoutException:get等待到最大的等待时间还未获得计算结果时。
*/
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException,TimeoutException;
}
接下来看看FutureTask 常见api:
package java.util.concurrent;
import java.util.concurrent.locks.LockSupport;
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
public class FutureTask<V> implements RunnableFuture<V> {
...
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
protected void set(V v) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
outcome = v;
U.putOrderedInt(this, STATE, NORMAL); // final state
finishCompletion();
}
}
...
}
1、首先看下包,会了解到一些信息,FutureTask也是并发包下的类,并且FutureTask底层基于LockSupport来实现,这点通过get方法可以看出。
2、其次看下类的继承关系FutureTask实现了Runnable, Future接口。所以FutureTask本质也是一个Runnable实现类,只是这个实现类内部进行了自己的包装。
3、最后看下常见的api,几乎实现了Future定义的所有方法,注意下构造的使用,最常用的还是构造传Callable实现类对象。
接下来看看最后一个相关类:
很简单,就是单独定义的一个接口,供FutureTask的构造使用。
public interface Callable<V> {
V call() throws Exception;
}
那么FutureTask是如何调用Callable的呢?这里就需要浅析下源码喽~
public class FutureTask<V> implements RunnableFuture<V> {
···
// 0、这个成员变量的值在FutureTask构造创建时被赋值。
private Callable<V> callable;
//Callable#call 执行的结果
private Object outcome;
public void run() {
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 1、执行Callable的call 方法,获取结果。
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
// 2、结果设置给outcome
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
protected void set(V v) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
// 3、结果设置给outcome
outcome = v;
U.putOrderedInt(this, STATE, NORMAL); // final state
finishCompletion();
}
}
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
//阻塞等待,内部通过LockSupport来实现。
s = awaitDone(false, 0L);
// 4、通过report取执行结果
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
// 返回执行结果
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
···
}
可见源码乍一看还是很简单的:
1、从表面看FutureTask就是特定的Runnable类,run方法给我们写好了,run内部调用的Callable#call,这个我们自己实现。
2、从整体流程看也很好理解,这里只需知道LockSupport也是并发包下的类,借助这个类我们很好实现同步阻塞。FutureTask#set 、FutureTask#get 流程在👆🏻已经做好了序号顺着很好理解。
实际应用
模拟一个工作中遇到的类似栗子
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
thread {
//1、子线程中操作bitmap,拿到结果立即更新
val originBitmap = getBitmap().get()
runOnUiThread {
image1.setImageBitmap(originBitmap)
}
//2、缩放,保存到本地。
val scaleBitmap = originBitmap.scale(originBitmap.width / 2, originBitmap.height / 2)
val path = cacheDir.absolutePath+"/" + UUID.randomUUID().toString() + ".png"
val file = File(path)
if (!file.exists()) {
file.createNewFile()
}
try {
val fos = FileOutputStream(file)
scaleBitmap.compress(Bitmap.CompressFormat.PNG, 80, fos)
fos.flush()
fos.close()
println("保存到本地:${file.absoluteFile}")
//System.out: 保存到本地:/data/user/0/com.example.bitmaptest/cache/c04b32b0-59bf-49c2-bfb0-4edf6ada682f.png
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun getBitmap(): FutureTask<Bitmap> {
val futureTask = FutureTask {
//获取Bitmap,作为Callable#call 返回值
BitmapFactory.decodeResource(resources, R.drawable.img)
}
// 线程池开启线程执行Runnable任务
Executors.newCachedThreadPool().execute(futureTask)
return futureTask
}
栗子还是很简单的,只是有一点需要留意这里执行futureTask这个Runnable时并没有直接使用Thread传参Runnable,而是使用了线程池,因为线程池也可以执行Runnable。
小结
之前对Feture的了解仅仅停留在👆🏻“入门写法”的最基础demo阶段,最近工作中碰到了,闲来稍微总结下感觉收获颇多的。又有了深入的了解。
这里还需要点明一点:使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方法都不是很好,因为受影响的线程总会被迫等待。从Java 8开始引入了CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
本文到此点到为止,后面的更深入的相关知识就需要以后继续探索了~