首页 > 代码库 > FutureTask——另一种闭锁的实现

FutureTask——另一种闭锁的实现

关于多线程,我们接触对多的,最基础,入门的可能就是实现Runnable接口继承Thead类,因为Java单继承的原因,通常建议是实现Runnable接口。但这种“简单”的线程会带来一个问题,写过的人都知道,不管是实现Runnable还是继承Thread类,他们都是实现public void run()方法,而这个方法可以看到是没有返回值的。但我们想执行一个线程得到它的返回值应该怎么做呢?此时FutureTask就要出场了。

首先我们还是需要定义一个线程,不同的是这个线程需要实现Callable接口。这是第一个不同的地方。

 1 package futuretask;
 2 
 3 import java.util.concurrent.Callable;
 4 
 5 /**
 6  * Created by yulinfeng on 12/17/16.
 7  */
 8 public class Task implements Callable<String> {
 9     private String name;
10 
11     public Task(String name){
12         this.name = name;
13     }
14 
15     @Override
16     public String call() throws Exception {
17         String hello = hello(name);
18         return hello;
19     }
20 
21     public String hello(String name){
22         return "hello " + name;
23     }
24 }

可以看到此时并不是实现run方法,而是一个叫call的方法,同时有返回值(返回值取决于所定义的泛型)。定义好这个线程过后,并不是直接将此线程的实例传入一个Thread类中执行start方法,这也是和没有返回值的线程不同的地方。

下面我们通过两种方法使用FutureTask,一个是通过线程池的方式创建线程,一个是直接创建一个线程。


通过多线程的方式创建线程:

 1 package futuretask;
 2 
 3 import countdownlatch.TaskThread;
 4 
 5 import java.util.concurrent.*;
 6 
 7 /**
 8  * Created by yulinfeng on 12/17/16.
 9  */
10 public class Test {
11     public static void main(String[] args){
12         ExecutorService exec = Executors.newCachedThreadPool();     //创建一个线程池
13         Future<String> hello = exec.submit(new Task("kevin"));  //执行线程池的submit方法,执行callable线程,并返回一个Future对象
14         try {
15             System.out.println(hello.get());    //通过调用Future的get方法,获取线程的返回值
16         } catch (InterruptedException e) {
17             e.printStackTrace();
18         } catch (ExecutionException e) {
19             e.printStackTrace();
20         }
21     }
22 }

这是第一种通过线程池创建线程的方式,通过调用线程的submit方法。如果是实现的Runnable方法,通过线程池创建线程执行的则是线程池的execute方法。一定注意调用线程返回值时使用的是Future的get方法来获取的返回值。


直接创建线程:

 1 package futuretask;
 2 
 3 import countdownlatch.TaskThread;
 4 
 5 import java.util.concurrent.*;
 6 
 7 /**
 8  * Created by yulinfeng on 12/17/16.
 9  */
10 public class Test {
11     public static void main(String[] args){
12         FutureTask<String> future = new FutureTask<String>(new Task("kevin"));  //将线程传入FuturaTask的构造方法中
13         Thread thread = new Thread(future); 
14         thread.start();
15         try {
16             System.out.println(future.get());
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         } catch (ExecutionException e) {
20             e.printStackTrace();
21         }
22     }
23 }

在调用get方法时,一定是在执行完线程后,不然将会一直阻塞。

其实仔细观察以上两张方法,第一种是Future接口,第二种是FutureTask类。FutureTask是Future接口的实现,为什么不能将Future接口传入Thread的构造方法中呢?原因很简单。在Thread的单个参数构造方法中,只能传入Runnable类型,而Future并没有继承Runnable接口,FutureTask则同时继承了Runnable接口和Future接口。

最后,那么FutureTask应该怎么来定义呢?其实和我们上一节将的CountDownLatch一样,它也是一种闭锁的实现。Future.get取决于任务的状态,如果任务已完成,那么get会立即返回结果,否则get将阻塞直到任务进入完成状态,然后返回结果或者抛出异常。FutureTask将计算结果从执行计算的线程传递到获取这个结果的线程,而FutureTask的规范确保了这种传递过程能实现结果的安全发布。——《Java并发编程实践》

 

FutureTask——另一种闭锁的实现