7.3 Callable与Future

2017-01-04 21:27:16 5,734 9

CallableFuture的作用是,我们可以启动一个线程去执行某个任务,而另外一个线程等待获取这个结果后执行响应的操作。

假设我们有这样一个案例,线程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)能够获取任务执行结果。