Callable与FutureTask

FutureTask是《java并发编程实战》这本书里讲到的同步工具类的第二种。
提FutureTask之前,需要先提及Callable接口。
一般来讲,java中创建线程常用两种方式,一个是继承Thread类,一个是实现Runnable接口。第三种创建线程的方法就是实现Callable接口,实现接口中的call()方法。
通过callable创建的线程,是带有返回结果的,通过的就是FutureTask来拿到执行结果。并且call()方法可以抛出异常。这两点是Runnable接口所不具备的。用Thread及Runnable接口创建的线程,想拿到执行结果,需要线程通信或线程共享变量这些方式。

Callable与FutureTask用法如下:

public class FutureTest {

	public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
		InnerCall in = new InnerCall(3);
		FutureTask<Integer> f = new FutureTask<Integer>(in);
		Thread t1 = new Thread(f);
		t1.start();
		t1.join();
		System.out.println(f.get());
	}

}

class InnerCall implements Callable<Integer>{
	
	private int i;
	
	public InnerCall(int i) {
		this.i = i;
	}

	@Override
	public Integer call() throws Exception {
		int result = i;
		for(int j = 0 ; j < 10 ; j++)
			result = result * i;
		return result;
	}
	
}

运行结果如下:

177147

实现Callable接口需要通过泛型定义返回值的数据类型。在上面的代码中,模拟传入参数的十次方计算。通过创建FutureTask<Integer>对象,将Callable实例传入构造方法中,然后new Thread对象(构造参数为FutureTask实例)进行运行。调用FutureTask的get()方法来获得计算结果。

如果当前计算尚未结束,get()方法会阻塞,直到计算完毕才会返回结果或抛出异常:

public class FutureTest {

	public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
		InnerCall in = new InnerCall(3);
		FutureTask<Integer> f = new FutureTask<Integer>(in);
		Thread t1 = new Thread(f);
		t1.start();
		System.out.println("计算中......");
		System.out.println(f.get());
	}

}

class InnerCall implements Callable<Integer>{
	
	private int i;
	
	public InnerCall(int i) {
		this.i = i;
	}

	@Override
	public Integer call() throws Exception {
		Thread.sleep(4000);
		int result = i;
		for(int j = 0 ; j < 10 ; j++)
			result = result * i;
		return result;
	}
	
}

输出结果:

计算中......
177147

这段代码中,在call()方法第一步先让线程休眠了4秒。于是main方法运行后,马上输出了第一行计算中......,第二行则等待了几秒后才输出计算结果。这就是get()方法在计算结果出来之前会进行阻塞,直到计算完毕。

FutureTask常用方法:

public class FutureTest {

	public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
		InnerCall in = new InnerCall(3);
		FutureTask<Integer> f = new FutureTask<Integer>(in);
		Thread t1 = new Thread(f);
		t1.start();
		while(!f.isDone()) {
			System.out.println("没算完呢......");
		}
		System.out.println(f.get());
	}

}

class InnerCall implements Callable<Integer>{
	
	private int i;
	
	public InnerCall(int i) {
		this.i = i;
	}

	@Override
	public Integer call() throws Exception {
		Thread.sleep(4000);
		int result = i;
		for(int j = 0 ; j < 10 ; j++)
			result = result * i;
		return result;
	}
	
}

输出结果:

......
没算完呢......
没算完呢......
没算完呢......
没算完呢......
没算完呢......
177147

isDone()方法判断任务是否执行完成,上面这段代码中做了循环,如果任务没执行完则会一直复读机式地输出,直到执行完成,跳出循环计算结果。

cancel方法用来取消任务,如果任务取消后再调用get方法,则会抛出异常:

public class FutureTest {

	public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
		InnerCall in = new InnerCall(3);
		FutureTask<Integer> f = new FutureTask<Integer>(in);
		Thread t1 = new Thread(f);
		t1.start();
		f.cancel(true);
		System.out.println(f.get());
	}

}

class InnerCall implements Callable<Integer>{
	
	private int i;
	
	public InnerCall(int i) {
		this.i = i;
	}

	@Override
	public Integer call() throws Exception {
		Thread.sleep(4000);
		int result = i;
		for(int j = 0 ; j < 10 ; j++)
			result = result * i;
		return result;
	}
	
}

抛出异常:

Exception in thread "main" java.util.concurrent.CancellationException

cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。

get方法可以指定等待时间,如果超时了,也会直接抛出异常:

public class FutureTest {

	public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
		InnerCall in = new InnerCall(3);
		FutureTask<Integer> f = new FutureTask<Integer>(in);
		Thread t1 = new Thread(f);
		t1.start();
		System.out.println(f.get(1000,TimeUnit.MILLISECONDS));
	}

}

class InnerCall implements Callable<Integer>{
	
	private int i;
	
	public InnerCall(int i) {
		this.i = i;
	}

	@Override
	public Integer call() throws Exception {
		Thread.sleep(4000);
		int result = i;
		for(int j = 0 ; j < 10 ; j++)
			result = result * i;
		return result;
	}
	
}

上面这段代码,get方法指定等待时间一秒,下面的计算至少要4秒以上,于是main方法直接抛出异常:

Exception in thread "main" java.util.concurrent.TimeoutException

线程池中用到Callable和FutureTask比较多。此外《java并发编程实战》这本书里还举了两个例子。一个是通过FutureTask提前进行计算,然后在需要的时候直接通过get方法得到结果,提高效率。另外一个是将FutureTask任务缓存在ConcurrentHashMap当中,当有同样参数的运算进行时,可以直接在缓存中的FutureTask通过get方法获得计算结果。为什么缓存FutureTask任务而不缓存计算结果呢,书中讲的原因是有可能在两个参数相同的线程并发访问时,发现缓存都没有自己的结果,于是都进行了计算,这样效率低,所以改为了缓存任务。

参考:
https://www.cnblogs.com/fengsehng/p/6048609.html
感谢大佬带飞

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值