在学习FutureTask之前,我们应该先了解一个接口——RunnableFuture
这个接口是什么呢,给大家看一下
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
这是一个很简单的接口吧。
继承了Runnable和Future类,runnable就不说了,runnable也是一个接口,并且也有一个run方法。
我们平时用runnable都是和Thread一起使用的。所以runnable就是执行的内容,到底要运行啥,我们自己来决定。不是有个待实现的run方法吗。
Future是什么呢,我在《ExecutorService和Future的关系》这篇文章有讲到这个东西。
这个东西大概就是可以得到运行结果的一个东西,当然他不仅仅可以得到运行结果,还可以中断任务。
所以RunnableFuture同时继承了Runnable和Future,也就是说。
RunnableFuture不但知道要运行什么,而且还有中断运行的能力。
不过RunnableFuture也只是一个接口而已,只是定义了这个接口应该干啥。如果我们非要在取消的方法里面写下载一张图片的代码,还不是可以。
闲话少说,RunnableFuture定义了一个东西,这个东西可以运行任务,还可以中断任务。
不过也只是定义而已,我们来看看RunnableFuture的具体实现类——FutureTask。
终于讲到正题了哈。
FutureTask的源码很少,不到500行,不过源码写的很厉害,但不推荐阅读。因为里面也牵扯了部分我们不认识的类。
既然FutureTask继承了RunnableFuture,并且是具体的实现类,说明我们可以直接使用这个东西了。
并且可以指定一个任务,要运行什么,而且也可以中断任务,所以这是一个很方便的类呢。
学习这个类的方法推荐一种,直接使用。
这个类继承Runnable,所以我们可以把他当runnable使用。runnable我们平时怎么使用的。
举个简单的例子:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("hi runnable");
}
};
new Thread(runnable).start();
所以,FutureTask也可以这样使用,举个例子。
FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("hi futureTask");
return null;
}
});
new Thread(futureTask).start();
感觉还是有点区别,还记得之前说过的吗,FutureTask不仅仅可以执行任务,还可以得到结果,所以这个类不是有一个泛型可以泛指结果吗。
既然有结果,在运行的过程中返回一个结果很合理吧。
这个结果可以是任何东西,我们可以举一个实际开发中会遇到的问题。——下载一张图片
例子:
FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
for (int i = 1; i <= 10; i++) {
Thread.sleep(500);
System.out.println("下载中:" + (i / 10.0 * 100) + "%" + "(假装耗时)");
}
return "图片";
}
});
new Thread(futureTask).start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
代码有点多了,看完看完。很简单的。
模拟下载一张图片,最后得到这种图片,所以运行结果是这样的。
下载中:10.0%(假装耗时)
下载中:20.0%(假装耗时)
下载中:30.0%(假装耗时)
下载中:40.0%(假装耗时)
下载中:50.0%(假装耗时)
下载中:60.0%(假装耗时)
下载中:70.0%(假装耗时)
下载中:80.0%(假装耗时)
下载中:90.0%(假装耗时)
下载中:100.0%(假装耗时)
图片
看完代码有没有什么疑问呢?
应该有疑问才对哈。
用线程下载这张图片,但是是在主线程直接打印,难道不会报空指针吗,应该还没有下载完毕就执行打印语句了啊,应该空指针啊。
这就是FutureTask的厉害之处,如果调用get方法,如果没有get到,会阻塞在get方法那里,直到get到为止。
我们多加两行代码,看看是否如此。
long startTime = System.currentTimeMillis();
System.out.println(futureTask.get());
System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
很简单的耗时判断吧?
运行结果:
图片
耗时:5047
所以会一直阻塞在那里,之前我在讲《ArrayBlockingQueue和LinkedBlockingQueue》的时候,有讲过这种阻塞,大概就是这种阻塞。
FutureTask可以被打断还记得吗,我们可以使用cancel(boolean mayInterruptIfRunning)
来打断任务。
参数的含义是,如果被打断了,是否还要继续执行下去。
感觉这个类讲的差不多了。
what a nice class!