Java多线程之 FutureTask:带有返回值的函数定义和调用方式

FutureTask 返回值的函数定义和调用

使用Runnable接口定义的任务是没有返回值的。很多时候,我们是有返回值的,为了解决这个问题,Java提供了Callable接口,可以返回指定类型的值。

但是这个接口本身是不具备执行能力的,所以Java中,还有一个FutureTask 类用于使用Callable接口定义带有返回值的任务。

使用示例

以下代码演示了定义和调用的整个过程。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class FutureTaskDemo {
	public static void test2() throws Execution{
	    // 基于 Lambda 的 Callable 接口,在new FutureTask中的Lambda表达式即是Callable接口的实现
		FutureTask<Integer> task = new FutureTask<>(() -> {
			int t = 0;
			for (int i = 0; i < 10; i++)
				t += i;
			return t;
		}); 

		// 使用Thread类执行task
		System.out.println("Start calling.");
		long t1 = System.nanoTime();
		new Thread(task).start();
		long result = task.get();
		long t2 = System.nanoTime();
		System.out.println("Finish calling.");
		System.out.printf("Result: %d, Time: %.3f ms.\n", result, (t2 - t1) / 1000000f);
	}
}

执行后的输出:

Start calling.

Finish calling.

Result: 45, Time: 13.620 ms.

Java多线程 FutureTask用法及解析

1 FutureTask概念

FutureTask一个可取消的异步计算,FutureTask 实现了Future的基本方法,提空 start cancel 操作,可以查询计算是否已经完成,并且可以获取计算的结果。

结果只可以在计算完成之后获取,get方法会阻塞当计算没有完成的时候,一旦计算已经完成,那么计算就不能再次启动或是取消。

一个FutureTask 可以用来包装一个 Callable 或是一个runnable对象。因为FurtureTask实现了Runnable方法,所以一个 FutureTask可以提交(submit)给一个Excutor执行(excution).

2 FutureTask使用场景

FutureTask可用于异步获取执行结果或取消执行任务的场景。

通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。

另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。

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

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

public class FutureTest1 {
	public static void main(String[] args) {
		Task task = new Task();// 新建异步任务
		FutureTask<Integer> future = new FutureTask<Integer>(task) {
			// 异步任务执行完成,回调
			@Override
			protected void done() {
				try {
					System.out.println("future.done():" + get());
				} catch (InterruptedException e) {
					e.printStackTrace();
				} catch (ExecutionException e) {
					e.printStackTrace();
				}
			}
		};
		// 创建线程池(使用了预定义的配置)
		ExecutorService executor = Executors.newCachedThreadPool();
		executor.execute(future);
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		// 可以取消异步任务
		// future.cancel(true);
		try {
			// 阻塞,等待异步任务执行完毕-获取异步任务的返回值
			System.out.println("future.get():" + future.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}
	// 异步任务
	static class Task implements Callable<Integer> {
		// 返回异步任务的执行结果
		@Override
		public Integer call() throws Exception {
			int i = 0;
			for (; i < 10; i++) {
				try {
					System.out.println(Thread.currentThread().getName() + "_"
							+ i);
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			return i;
		}
	}
}

2.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(根据业务需求,自定义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(根据业务需求,自定义Connection)
private Connection createConnection(){
    return null;
}

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

3 部分源码分析

3.1 构造方法

public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
 }

3.2 cancel

//这个方法有一个参数 是否中断running
     public boolean cancel(boolean mayInterruptIfRunning) {
          /**
          * 这个有点晕啊逻辑关系是
          * 等价与 if(state!=new || !UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))
          * 这个意思是 如果state不是new 那么就退出方法,这时的任务任务坑是已经完成了 或是被取消了 或是被中断了
          * 如果是state 是new 就设置state 为中断状态 或是取消状态
          *
          **/
        if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            //如果是可中断 那么就 调用系统中断方法 然后把状态设置成INTERRUPTED
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java常见面试题之多线程和高并发详解

    volatile 对 volatile的理解 volatile 是一种轻量级的同步机制. 保证数据可见性 不保证原子性 禁止指令重排序 JMM JMM(Java 内存模型)是一种抽象的概念,描述了一组规则或规范,定义了程序中各个变量的访问方式. JVM运行程序的实体是线程,每个线程创建时 JVM 都会为其创建一个工作内存,是线程的私有数据区域.JMM中规定所有变量都存储在主内存,主内存是共享内存.线程对变量的操作在工作内存中进行,首先将变量从主内存拷贝到工作内存,操作完成后写会主内存.不同线程间

  • Java利用future及时获取多线程运行结果

    Future接口是Java标准API的一部分,在java.util.concurrent包中.Future接口是Java线程Future模式的实现,可以来进行异步计算. 有了Future就可以进行三段式的编程了,1.启动多线程任务2.处理其他事3.收集多线程任务结果.从而实现了非阻塞的任务调用.在途中遇到一个问题,那就是虽然能异步获取结果,但是Future的结果需要通过isdone来判断是否有结果,或者使用get()函数来阻塞式获取执行结果.这样就不能实时跟踪其他线程的结果状态了,所以直接使用g

  • Java多线程之FutureTask的介绍及使用

    一.FutureTask的理解 FutureTask属于java.util.concurrent 包:FutureTask表示可取消的异步计算.FutureTask类提供了一个Future的基本实现 ,具有启动和取消计算的方法,查询计算是否完整,并检索计算结果.结果只能在计算完成后才能检索; 如果计算尚未完成,则get方法将阻止. 一旦计算完成,则无法重新启动或取消计算(除非使用runAndReset()调用计算 ). 二.FutureTask类图 从上面的FutureTask类图中可以看出,F

  • Java线程池FutureTask实现原理详解

    前言 线程池可以并发执行多个任务,有些时候,我们可能想要跟踪任务的执行结果,甚至在一定时间内,如果任务没有执行完成,我们可能还想要取消任务的执行,为了支持这一特性,ThreadPoolExecutor提供了 FutureTask 用于追踪任务的执行和取消.本篇介绍FutureTask的实现原理. 类视图 为了更好的理解FutureTask的实现原理,这里先提供几个重要接口和类的结构,如下图所示: RunnableAdapter ThreadPoolExecutor提供了submit接口用于提交任

  • java多线程返回值使用示例(callable与futuretask)

    Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值,下面来看一个简单的例子 复制代码 代码如下: package com.future.test; import java.io.FileNotFoundException;import java.io.IOException;i

  • Java多线程之 FutureTask:带有返回值的函数定义和调用方式

    FutureTask 返回值的函数定义和调用 使用Runnable接口定义的任务是没有返回值的.很多时候,我们是有返回值的,为了解决这个问题,Java提供了Callable接口,可以返回指定类型的值. 但是这个接口本身是不具备执行能力的,所以Java中,还有一个FutureTask 类用于使用Callable接口定义带有返回值的任务. 使用示例 以下代码演示了定义和调用的整个过程. import java.util.concurrent.Callable; import java.util.co

  • Java多线程并发FutureTask使用详解

    目录 基本使用 代码分析 继承关系 Future RunnableFuture FutureTask 状态 属性 内部类 构造方法 检索 FutureTask 状态 取消操作 计算结果 立刻获取结果或异常 run 方法组 本文基于最新的 OpenJDK 代码,预计发行版本为 19 . Java 的多线程机制本质上能够完成两件事情,异步计算和并发.并发问题通过解决线程安全的一系列 API 来解决:而异步计算,常见的使用是 Runnable 和 Callable 配合线程使用. FutureTask

  • 浅谈Java中的this作为返回值时返回的是什么

    有时会遇到this作为返回值的情况,那么此时返回的到底是什么呢? 返回的是调用this所处方法的那个对象的引用,读起来有点绕口哈,有没有想起小学语文分析句子成份的试题,哈哈. 一点点分析的话,主干是"返回的是引用": 什么引用呢?"那个对象的引用": 哪个对象呢?"调用方法的那个对象": 调用的哪个方法呢?"调用的是this所位于的方法":这样就清楚了. 再总结一下就是,this作为返回值时,返回的是调用某方法的对象的引用,这

  • java中有无参数和返回值的方法详解

    目录 java有无参数和返回值 以下的例子要细细查看 方法的返回值和参数 1.返回值 2.参数 java有无参数和返回值 首先,定义一个土豪类(LocalTyrant) 属性:name moeney smoke 行为一:(无参数无返回值): 行为二:(无参数有返回值): 行为三:(有参数有返回值): 行为四:(有参数无返回值): 以下的例子要细细查看 慢慢分析,切不可急于求成 package cm.tr; class LocalTyrant{ String name; int money; St

  • Java 带参数与带返回值的方法的定义和调用

    目录 带参数方法的定义和调用 形参和实参 带参数方法练习 带返回值的方法的定义和调用 带返回值的方法定义 带返回值的方法调用 带参数方法的定义和调用 形参和实参 形参:方法定义中的参数 相当于变量定义格式,例int number 实参:方法调用中参数 等同于变量或常量,例如10   , number 带参数方法练习 需求: 设计一个方法用于打印两个数中最大数,数据来自于方法参数 思路: 1.定义一个方法,用于打印两个书中的最大数,例如getMax() public static void get

  • Java多线程导致CPU占用100%解决及线程池正确关闭方式

    简介 情景:1000万表数据导入内存数据库,按分页大小10000查询,多线程,15条线程跑. 使用了ExecutorService executor = Executors.newFixedThreadPool(15) 本地跑了一段时间后,发现电脑CPU逐渐升高,最后CPU占用100%卡死,内存使用也高达80%. 排查问题 Debug 发现虽然创建了定长15的线程池,但是因为数据量大,在For中循环分页查询的List会持续加入LinkedBlockingQueue() 队列中每一个等待的任务,又

  • php定义一个参数带有默认值的函数实例分析

    本文实例分析了php定义一个参数带有默认值的函数用法.分享给大家供大家参考.具体分析如下: php的函数参数可以指定默认值,指定默认值后,调用时如果不给该参数赋值,则该参数就使用默认值 <?php function capitalize( $str, $each=TRUE ) { $str = strtolower($str); if ($each === TRUE) { $str = ucwords ($str); } else { $str = strtoupper($str); } ech

  • javascript少儿编程关于返回值的函数内容

    带返回值的函数 好吧,我们把alert(sum)一行改成下面的代码: return sum; return后面的值叫做返回值.使用下面的语句调用函数就可以将这个返回值存储在变量中了. result = add2(3,4); 该语句执行后,result变量中的值为7.值得说明的是,我们的函数中,参数和返回值都是数字,其实它们也可以是字符串等其它类型.

  • C语言中带返回值的宏定义方式

    目录 C语言中带返回值的宏定义 宏定义编写 宏定义分析 宏定义验证 经验总结 C语言中一些宏定义和常用的函数 typeof 关键字 snprintf()函数的作用 __builtin_expect的作用 C语言中常用的预定义 反斜杠的作用 总结 C语言中带返回值的宏定义 相信大家在实际工作中,一定有遇到需要编写一个宏定义,且希望它能带返回值的场景吧? 比如我之前就遇到一个场景,早期的代码是使用函数实现的功能,现在想换成宏定义,但是又要保留之前调用函数的代码不动,这样我就只能想办法写一个带返回值的

  • Java 多线程并发FutureTask

    本文基于最新的 OpenJDK 代码,预计发行版本为 19 . Java 的多线程机制本质上能够完成两件事情,异步计算和并发.并发问题通过解决线程安全的一系列 API 来解决:而异步计算,常见的使用是 Runnable 和 Callable 配合线程使用. FutureTask 是基于 Runnable 实现的一个可取消的异步调用 API . 基本使用 Future 代表了异步计算的结果,通过 ExecutorService 的 Future<?> submit(Runnable task)

随机推荐