Java并发编程(1)- Callable、Future和FutureTask

2021/11/24 12:10:37

本文主要是介绍Java并发编程(1)- Callable、Future和FutureTask,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

撸过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



这篇关于Java并发编程(1)- Callable、Future和FutureTask的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程