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)能够获取任务执行结果。