FutureTask的用法及两种常用的使用场景

原创 2014年03月28日 15:00:02

FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。

1. FutureTask执行多任务计算的使用场景

利用FutureTask和ExecutorService,可以用多线程的方式提交计算任务,主线程继续执行其他任务,当主线程需要子线程的计算结果时,在异步获取子线程的执行结果。

package futuretask;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class FutureTaskForMultiCompute {
    
    public static void main(String[] args) {
        
        FutureTaskForMultiCompute inst=new FutureTaskForMultiCompute();
        // 创建任务集合
        List<FutureTask<Integer>> taskList = new ArrayList<FutureTask<Integer>>();
        // 创建线程池
        ExecutorService exec = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            // 传入Callable对象创建FutureTask对象
            FutureTask<Integer> ft = new FutureTask<Integer>(inst.new ComputeTask(i, ""+i));
            taskList.add(ft);
            // 提交给线程池执行任务,也可以通过exec.invokeAll(taskList)一次性提交所有任务;
            exec.submit(ft);
        }
        
        System.out.println("所有计算任务提交完毕, 主线程接着干其他事情!");

        // 开始统计各计算线程计算结果
        Integer totalResult = 0;
        for (FutureTask<Integer> ft : taskList) {
            try {
                //FutureTask的get方法会自动阻塞,直到获取计算结果为止
                totalResult = totalResult + ft.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        // 关闭线程池
        exec.shutdown();
        System.out.println("多任务计算后的总结果是:" + totalResult);

    }

    private class ComputeTask implements Callable<Integer> {

        private Integer result = 0;
        private String taskName = "";
        
        public ComputeTask(Integer iniResult, String taskName){
            result = iniResult;
            this.taskName = taskName;
            System.out.println("生成子线程计算任务: "+taskName);
        }
        
        public String getTaskName(){
            return this.taskName;
        }
        
        @Override
        public Integer call() throws Exception {
            // TODO Auto-generated method stub

            for (int i = 0; i < 100; i++) {
                result =+ i;
            }
            // 休眠5秒钟,观察主线程行为,预期的结果是主线程会继续执行,到要取得FutureTask的结果是等待直至完成。
            Thread.sleep(5000);
            System.out.println("子线程计算任务: "+taskName+" 执行完成!");
            return result;
        }
    }
}

2. FutureTask在高并发环境下确保任务只执行一次

在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景,通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下面所示:

    private Map<String, Connection> connectionPool = new HashMap<String, Connection>();
    private ReentrantLock lock = new ReentrantLock();
    
    public Connection getConnection(String key){
        try{
            lock.lock();
            if(connectionPool.containsKey(key)){
                return connectionPool.get(key);
            }
            else{
                //创建 Connection
                Connection conn = createConnection();
                connectionPool.put(key, conn);
                return conn;
            }
        }
        finally{
            lock.unlock();
        }
    }
    
    //创建Connection
    private Connection createConnection(){
        return null;
    }

在上面的例子中,我们通过加锁确保高并发环境下的线程安全,也确保了connection只创建一次,然而确牺牲了性能。改用ConcurrentHash的情况下,几乎可以避免加锁的操作,性能大大提高,但是在高并发的情况下有可能出现Connection被创建多次的现象。这时最需要解决的问题就是当key不存在时,创建Connection的动作能放在connectionPool之后执行,这正是FutureTask发挥作用的时机,基于ConcurrentHashMap和FutureTask的改造代码如下:

    private ConcurrentHashMap<String,FutureTask<Connection>>connectionPool = new ConcurrentHashMap<String, FutureTask<Connection>>();
    
    public Connection getConnection(String key) throws Exception{
        FutureTask<Connection>connectionTask=connectionPool.get(key);
        if(connectionTask!=null){
            return connectionTask.get();
        }
        else{
            Callable<Connection> callable = new Callable<Connection>(){
                @Override
                public Connection call() throws Exception {
                    // TODO Auto-generated method stub
                    return createConnection();
                }
            };
            FutureTask<Connection>newTask = new FutureTask<Connection>(callable);
            connectionTask = connectionPool.putIfAbsent(key, newTask);
            if(connectionTask==null){
                connectionTask = newTask;
                connectionTask.run();
            }
            return connectionTask.get();
        }
    }
    
    //创建Connection
    private Connection createConnection(){
        return null;
    }

经过这样的改造,可以避免由于并发带来的多次创建连接及锁的出现。



Future和FutureTask的使用

java中提供了futuretask供异步任务使用。可以将一个任务提交给后台线程执行,当需要执行结果的时候调用task.get函数。 如果后台线程还没有执行完,那么task.get将会阻塞,直到后台线...
  • naughty610
  • naughty610
  • 2014-08-31 17:44:18
  • 8527

FutureTask 深度解析

FutureTask 深度解析
  • liulipuo
  • liulipuo
  • 2014-09-03 17:31:25
  • 31629

Java多线程编程:Callable、Future和FutureTask浅析(多线程编程之四)

通过前面几篇的学习,我们知道创建线程的方式有两种,一种是实现Runnable接口,另一种是继承Thread,但是这两种方式都有个缺点,那就是在任务执行完成之后无法获取返回结果,那如果我们想要获取返回结...
  • javazejian
  • javazejian
  • 2016-03-15 15:09:24
  • 35387

Java中的Runnable、Callable、Future、FutureTask的区别和CompletionService的使用场景

Java中存在Runnable、Callable、Future、FutureTask这几个与线程相关的类或者接口,在Java中也是比较重要的几个概念,我们通过下面的简单示例来了解一下它们的作用于区别。...
  • jdsjlzx
  • jdsjlzx
  • 2016-10-24 16:17:40
  • 3644

高级并发编程学习-callable与Future的使用

FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过Fu...
  • leixingbang1989
  • leixingbang1989
  • 2016-01-11 19:05:28
  • 1917

FutureTask的用法

使用Future Task 来提前加载稍后需要的数据
  • jianlong1284537512
  • jianlong1284537512
  • 2016-01-15 14:12:31
  • 189

通过FutureTask来实现阻塞当前线程等待其他线程处理的结果

最近在开发中遇到一个这样的需求,如:定义一个接口给第三方调用,方法返回的结果需要通过异步任务返回。我们可以利用FutureTask很轻松的实现,还是直接上代码吧。先自定义一个FutureTask:pr...
  • Kalwang
  • Kalwang
  • 2015-07-21 14:54:46
  • 1648

Java进阶之FutureTask的用法及解析

1 FutureTask概念FutureTask一个可取消的异步计算,FutureTask 实现了Future的基本方法,提空 start cancel 操作,可以查询计算是否已经完成,并且可以获取计...
  • chenliguan
  • chenliguan
  • 2017-01-11 17:47:49
  • 6063

java多线程Future、FutureTask使用示例,返回异步的结果

1、Future、FutureTask介绍Future是一个接口,该接口用来返回异步的结果。FutureTask是一个类,是Future 的一个实现。2、主要方法future.get(); 或者 fu...
  • javaloveiphone
  • javaloveiphone
  • 2017-02-07 14:37:59
  • 5147

Future, FutureTask的作用和差异

Future是一个接口,表示一个任务的周期,并提供了相应的方法来判断是否已经完成或者取消任务,以及获取任务的结果和取消任务。 下面根据Future的定义介绍一下相关的接口 1. boolean...
  • huangmeimao
  • huangmeimao
  • 2016-09-26 14:31:14
  • 857
收藏助手
不良信息举报
您举报文章:FutureTask的用法及两种常用的使用场景
举报原因:
原因补充:

(最多只允许输入30个字)