7.3 Callable与Future
Callable
与Future
的作用是,我们可以启动一个线程去执行某个任务,而另外一个线程等待获取这个结果后执行响应的操作。
假设我们有这样一个案例,线程A中进行某种运算,而主线程需要等待其运算结果,以便进行接下来的操作。
1、传统实现方式
在没有使用Callable与Future之前,我们要实现这样的效果,通常需要通过共享变量
和自旋锁
实现(或者使用wait、notify)。使用自旋锁的代码案例如下:
import java.util.Date; public class SpinLock { public static String sharedVariable;//共享变量 public static void main(String[] args) { //启动一个线程执行运行 new Thread(){ @Override public void run() { try { Thread.sleep(2000);//进行运算操作,以休眠代替 sharedVariable="Hello"; } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); System.out.println("开始时间:"+new Date()); //自旋锁,就是不断进行循环,起到阻塞的作用 while(sharedVariable==null){} System.out.println(sharedVariable); System.out.println("结束时间:"+new Date()); }; }
这段程序的作用是检测sharedVariable不为空的情况下,主线程才能继续往下执行。由于sharedVariable是在另外一个线程中执行,因此主线程必须不断的去检测,这是通过一个死循环来完成。一旦检测到sharedVariable不为空,则打印出sharedVariable的内容。因为我们在子线程中是休眠了2秒,所以主线程必须是等待2秒后,才能得到结果。是实际开发中,我们的具体操作代码可能会代理休眠语句。
运行程序,控制台输出如下:
开始时间:Sun Feb 28 12:25:00 CST 2016 Hello 结束时间:Sun Feb 28 12:25:02 CST 2016
可以看到结果与我们的预期是一样的。
2、使用Callable与Future
在之前的代码中,我们使用的是“自旋锁+共享变量”的方式来完成案例的需求,下面我们看一下使用Callable与Future怎样实现。
一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:
<T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task);
第一个submit方法里面的参数类型就是Callable。
暂时只需要知道Callable一般是和ExecutorService配合来使用的,具体的使用方法讲在后面讲述。
一般情况下我们使用第一个submit方法和第三个submit方法,第二个submit方法很少使用。
public class CallableAndFuture { public static void main(String[] args) { System.out.println("开始时间:" + new Date()); ExecutorService service = Executors.newSingleThreadExecutor(); //Future与Callable中的泛型,就是返回值的类型 Future<String> future = service.submit(new Callable<String>() { public String call() throws Exception { Thread.sleep(2000); return "Hello"; } }); try { String result = future.get();// 该方法会进行阻塞,等待执行完成 System.out.println(result); } catch (Exception e) { e.printStackTrace(); } System.out.println("结束时间:" + new Date()); service.shutdown(); } }
这段代码的运行结果与自旋锁的案例是一致的。需要说明的是Callable<T>和Future<T>都有一个泛型参数,这指的是线程运行返回值结果类型。调用future.get()
方法,会进行阻塞,直到线程运行完并返回结果。
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
Future类位于java.util.concurrent包下,它是一个接口:
public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
在Future接口中声明了5个方法,下面依次解释每个方法的作用:
cancel方法:用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning
表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
isCancelled方法:表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
isDone方法:表示任务是否已经完成,若任务完成,则返回true;
get()方法:用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
get(long timeout, TimeUnit unit):用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
也就是说Future提供了三种功能:
1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。