关于多线程,我们接触对多的,最基础,入门的可能就是实现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并发编程实践》