撸过JavaSE(即Java基础技术栈)的小伙伴都知道,实现多线程有两种方式,一种是继承Thread,即extends Thread 然后实现其中的run()方法;另外一种是实现Runnable接口,即implements Runnable,然后实现其中的run()方法;仔细观察这两种方式,会发现这两者都不能返回线程异步执行完的结果,但在实际项目开发中却偶尔需要获取其中的返回结果,咋办嘞?于是乎Callable和Future就排上用场了,本文我们将对其做一番详尽的介绍!
还是先介绍下多线程的传统实现方式吧,如下代码所示:
public class ThreadUtil {
public static void main(String[] args) throws Exception{
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("---子线程正在执行---"+Thread.currentThread().getName());
Map<String,Object> dataMap=Maps.newHashMap();
dataMap.put("id",10010);
dataMap.put("name","steadyjack");
dataMap.put("nickName","多隆");
System.out.println("---子线程执行后得到的结果:"+dataMap);
}
});
try {
thread.start();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("---主线程正在执行---"+Thread.currentThread().getName());
}
}
在上述代码中,我们首先通过一个实现Runnable接口的匿名实现类创建了一个线程对象实例,即thread,并编写实现了其中run()方法的代码逻辑(而这就是该线程要执行的任务),主要是构造一个私有变量Map<String,Object>,并将相关的数据塞入进去。
点击运行该代码后,显而易见可以预测其运行结果:
从上述编写的代码以及运行结果来看,会发现如果我们想获取得到dataMap的内容是很困难的,因为run()方法的返回值为void;当然啦,也不是完全没有办法,在上面的条件下,如果想要获取到dataMap并做进一步的操作的话,则可以将dataMap定义为全局的共享变量,或者使用线程通信的方式来达到效果,如下所示为通过共享全局变量的方式:
public class ThreadUtil {
private static final Map<String,Object> dataMap=Maps.newHashMap();
………
}
之后就可以在该类的其他地方使用了!
但这种方式有个很明显的弊端,那就是多线程共享、并发访问可能会出现安全性问题,即如果开启10个线程,每个线程需要对dataMap里头的key,即id 加1,在高并发的情况下其最终的运行效果很可能不一定是 10020 (因为初始值为10010,每个线程加1次,10个线程下来就是加10次,理想情况下为10020),如下图所示:
但有时候我们在项目里头既要用到异步(为了解耦)、也想要获取异步执行的结果,可以说是“鱼和熊掌皆想兼得”:
于是乎这个重任就落到了Callable和Future身上了,这是JDK从1.5版本开始就已经提供了,可以通过它们实现在任务异步执行完毕之后得到任务的执行结果。
看到这里,可能有些小伙伴会发问:为什么通过Callable就可以获取到线程异步执行的结果呢?这一切还得回归到源码身上,如下所示为Callable的定义:
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
会发现它跟Runnable一样都是接口,但区别在于创建Callable时可以传入一个泛型V,而这个泛型类型V会发现真是call()方法执行后返回的结果(call()方法的作用类似于run()方法,反正都是指一个线程要执行的任务),OK,到此谜底就解开了!
那么怎么使用Callable呢?在Java里面可以通过调用ExecutorService类里面的相关API来使用Callable,如下图所示:
仔细观察上图,会发现如果想要获取线程执行Callable类型任务后的结果时,需要通过Future进行获取,那么Future为何物呢?
更多请见:http://www.mark-to-win.com/tutorial/51113.html