在多线程开发中,我们经常用到的一个是Thread类,或Runnable接口,但他们不太方便返回执行的结果,所以这里介绍一个新的类,它即能达到多线程异步执行的目标,还能方便的得到结果,这个类就是Callable类。下面我们主要通过代码学习具体的使用,关键部分都在代码中作了注释。
代码:MyCallableTask.java
自定义一个类MyCallableTask,该类继承Callable接口,我们重写call方法,增加一个延时处理让线程sleep2秒的时间,然后返回一个int型数字。
package com.test;
import java.util.concurrent.Callable;
public class MyCallableTask implements Callable {
@Override
public Integer call() throws Exception {
System.out.println("子线程执行中.......");
//下面我们给子线程人为延长2秒的执行时间
Thread.sleep(2000);
int n=0;
for(int i=0;i<100;i++){
n+=i;
}
return n;
}
}
CallableTest.java
下面我们开始主类的编写,因为要执行Callable任务,我们这里把ExecutorService拿出来,它是可以执行Runnable, Callable的线程的。
package com.test;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) {
//实例 化线程池对象
ExecutorService es = Executors.newCachedThreadPool();
MyCallableTask mct = new MyCallableTask();
//使用ExecutorService线程池实例提交callable任务,返回Future对象用于对callable任务进行管理,比如 取消、get等操作
Future fu = es.submit(mct);//使用ExecutorService启动Callable
//使用FutureTask创建一个任务,通过Callable构造实例
FutureTask ft=new FutureTask<>(mct);
es.submit(ft);//使用ExecutorService启动FutureTask
es.shutdown();
System.out.println("主线程开始........");
//我们让主线程等待4秒,令子线程的call任务能执行完,因此才可以使用get获取返回结果
try {
Thread.sleep(4000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//我们得到submit返回的Future对象,因此下面我们就可以对callable进行cancel、get等操作了
//因为上面主线程等待了4秒,因此到这里子线程已经结束 了,我们下面的cancel操作将会返回false,如果我们将上面的等待时间由3000,改成1000,那样小于子线程的执行时间,因此是返回true
boolean iscan = fu.cancel(true);
if (iscan) {
System.out.println("子线程取消了........");
} else{
try {
System.out.println("第一个任务返回:"+fu.get());
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
System.out.println("第二个任务返回:"+ft.get());
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
返回结果:
子线程执行中.......
子线程执行中.......
主线程开始........
第一个任务返回:4950
第二个任务返回:4950
如果我们让主类停留的时间短一点(比子线程短),这时我们下面的代码,取消子线程任务的操作就可以生效了,比如我们把上面的4000,改成1000(也就是停留1秒),看看执行的效果是:
子线程执行中.......
子线程执行中.......
主线程开始........
子线程取消了........
第二个任务返回:4950
看到上面的运行情况 了吧,也就是说当我们使用ExecutorService.submit方法提交任务后,我们不仅能拿到结果,还可以在子任务执行完成以前对其进行cancel操作。
至于Future,FutureTask它们的区别在哪里?
我们看上面的代码,我们调用 es.submit(mct)时(mct是一个Callable对象)会返回一个Future对象,这是让我们对Callable进行管理的对象,也使用这个对象进行最后的get获取结果。
我们使用FutureTask时,方法就不太一样了,我们使用es.submit(ft),这里ft是我们前面创建的一个FutureTask对象,es.submit(ft)后,我们并不是像上面那样能得到一个Future,然后使用Future.get获取,这里我们是直接使用ft对象(即FutureTask)的方法获取get。
区别我们可以这么理解
FutureTask是对Future的管理封装,事实上FutureTask继承了Runnable与Future接口。所以就可以理解为我提交Callable的时候,线程池给我返回Future,我用它来操作和获取结果。当提交FutureTask的时候,就是说我要自己管理了,我提交这个任务,不需要返回什么,后面我自己操作和获取结果。
至于原因吗?细心的同学可以研究一下java源码,我们可以从中找到原因:
Executors.newCachedThreadPool()返回的是ThreadPoolExecutor
ThreadPoolExecutor又扩展了抽象类:AbstractExecutorService
最终上面submit的方法就在AbstractExecutorService中
AbstractExecutorService.java
在AbstractExecutorService文件中,submit有三个
看上面代码,当我们提交submit(Callable)时,其实也是将其封装至一个FutureTask对象中了。所以最终意义就是如果我们提交的是Callable对象,那么java的AbstractExecutorService类帮我们new一个FutureTask。