FutureTask是《java并发编程实战》这本书里讲到的同步工具类的第二种。
提FutureTask之前,需要先提及Callable接口。
一般来讲,java中创建线程常用两种方式,一个是继承Thread类,一个是实现Runnable接口。第三种创建线程的方法就是实现Callable接口,实现接口中的call()方法。
通过callable创建的线程,是带有返回结果的,通过的就是FutureTask来拿到执行结果。并且call()方法可以抛出异常。这两点是Runnable接口所不具备的。用Thread及Runnable接口创建的线程,想拿到执行结果,需要线程通信或线程共享变量这些方式。
Callable与FutureTask用法如下:
public class FutureTest {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
InnerCall in = new InnerCall(3);
FutureTask<Integer> f = new FutureTask<Integer>(in);
Thread t1 = new Thread(f);
t1.start();
t1.join();
System.out.println(f.get());
}
}
class InnerCall implements Callable<Integer>{
private int i;
public InnerCall(int i) {
this.i = i;
}
@Override
public Integer call() throws Exception {
int result = i;
for(int j = 0 ; j < 10 ; j++)
result = result * i;
return result;
}
}
运行结果如下:
177147
实现Callable接口需要通过泛型定义返回值的数据类型。在上面的代码中,模拟传入参数的十次方计算。通过创建FutureTask<Integer>
对象,将Callable实例传入构造方法中,然后new Thread对象(构造参数为FutureTask实例)进行运行。调用FutureTask的get()方法来获得计算结果。
如果当前计算尚未结束,get()方法会阻塞,直到计算完毕才会返回结果或抛出异常:
public class FutureTest {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
InnerCall in = new InnerCall(3);
FutureTask<Integer> f = new FutureTask<Integer>(in);
Thread t1 = new Thread(f);
t1.start();
System.out.println("计算中......");
System.out.println(f.get());
}
}
class InnerCall implements Callable<Integer>{
private int i;
public InnerCall(int i) {
this.i = i;
}
@Override
public Integer call() throws Exception {
Thread.sleep(4000);
int result = i;
for(int j = 0 ; j < 10 ; j++)
result = result * i;
return result;
}
}
输出结果:
计算中......
177147
这段代码中,在call()方法第一步先让线程休眠了4秒。于是main方法运行后,马上输出了第一行计算中......
,第二行则等待了几秒后才输出计算结果。这就是get()方法在计算结果出来之前会进行阻塞,直到计算完毕。
FutureTask常用方法:
public class FutureTest {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
InnerCall in = new InnerCall(3);
FutureTask<Integer> f = new FutureTask<Integer>(in);
Thread t1 = new Thread(f);
t1.start();
while(!f.isDone()) {
System.out.println("没算完呢......");
}
System.out.println(f.get());
}
}
class InnerCall implements Callable<Integer>{
private int i;
public InnerCall(int i) {
this.i = i;
}
@Override
public Integer call() throws Exception {
Thread.sleep(4000);
int result = i;
for(int j = 0 ; j < 10 ; j++)
result = result * i;
return result;
}
}
输出结果:
......
没算完呢......
没算完呢......
没算完呢......
没算完呢......
没算完呢......
177147
isDone()方法判断任务是否执行完成,上面这段代码中做了循环,如果任务没执行完则会一直复读机式地输出,直到执行完成,跳出循环计算结果。
cancel方法用来取消任务,如果任务取消后再调用get方法,则会抛出异常:
public class FutureTest {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
InnerCall in = new InnerCall(3);
FutureTask<Integer> f = new FutureTask<Integer>(in);
Thread t1 = new Thread(f);
t1.start();
f.cancel(true);
System.out.println(f.get());
}
}
class InnerCall implements Callable<Integer>{
private int i;
public InnerCall(int i) {
this.i = i;
}
@Override
public Integer call() throws Exception {
Thread.sleep(4000);
int result = i;
for(int j = 0 ; j < 10 ; j++)
result = result * i;
return result;
}
}
抛出异常:
Exception in thread "main" java.util.concurrent.CancellationException
cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
get方法可以指定等待时间,如果超时了,也会直接抛出异常:
public class FutureTest {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
InnerCall in = new InnerCall(3);
FutureTask<Integer> f = new FutureTask<Integer>(in);
Thread t1 = new Thread(f);
t1.start();
System.out.println(f.get(1000,TimeUnit.MILLISECONDS));
}
}
class InnerCall implements Callable<Integer>{
private int i;
public InnerCall(int i) {
this.i = i;
}
@Override
public Integer call() throws Exception {
Thread.sleep(4000);
int result = i;
for(int j = 0 ; j < 10 ; j++)
result = result * i;
return result;
}
}
上面这段代码,get方法指定等待时间一秒,下面的计算至少要4秒以上,于是main方法直接抛出异常:
Exception in thread "main" java.util.concurrent.TimeoutException
线程池中用到Callable和FutureTask比较多。此外《java并发编程实战》这本书里还举了两个例子。一个是通过FutureTask提前进行计算,然后在需要的时候直接通过get方法得到结果,提高效率。另外一个是将FutureTask任务缓存在ConcurrentHashMap当中,当有同样参数的运算进行时,可以直接在缓存中的FutureTask通过get方法获得计算结果。为什么缓存FutureTask任务而不缓存计算结果呢,书中讲的原因是有可能在两个参数相同的线程并发访问时,发现缓存都没有自己的结果,于是都进行了计算,这样效率低,所以改为了缓存任务。