JAVA多线程高并发学习笔记(三)——Callable、Future和FutureTask

简介: 为什么要是用Callable和Future Runnable的局限性 Executor采用Runnable作为基本的表达形式,虽然Runnable的run方法能够写入日志,写入文件,写入数据库等操作,但是它不能返回一个值,或者抛出一个受检查的异常,有些需要返回值的需求就不能满足了。

为什么要是用Callable和Future

Runnable的局限性

Executor采用Runnable作为基本的表达形式,虽然Runnable的run方法能够写入日志,写入文件,写入数据库等操作,但是它不能返回一个值,或者抛出一个受检查的异常,有些需要返回值的需求就不能满足了。

能够取消

Executor中的任务有四个状态:创建,提交,开始和完成。如果说有些任务执行时间比较长,希望能够取消该任务,Executor中的任务在未开始前是可以取消的,如果已经开始了,只能通过中断的方式来取消。如果使用Callable和Future的结合,可以使用Future的canel方法取消任务,这样就方便多了。

 

一个例子:

import java.util.concurrent.*;

public class Demo1 {

    public static void main(String args[]) throws Exception {
        ServiceTask task = new ServiceTask();
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<Integer> result = executor.submit(task);
        executor.shutdown();
        System.out.println("正在执行任务");
        Thread.sleep(1000);
        System.out.println("task运行结果为:" + result.get());
    }
}

class ServiceTask implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        Thread.sleep(2000);
        int result = 0;
        // 假设一个很庞大的计算
        for(int i=1;i<100;i++){
            for (int j=0;j<i;j++){
                result +=j;
            }
        }
        return result;
    }
}

看一下执行结果:

这个例子就是一个非常简单的使用Callable和Futute的例子,ServiceTask类实现了Callable接口,并返回一个Integer类型的值。

Future<Integer> result = executor.submit(task);这行代码就是构造一个Future。使用其get()方法就能得到最后的运行值。

 好了看完这一个简单的例子,那就来仔细了解一下它们。

了解Callable和Future

Callable

 来看一下callable的代码:

public abstract interface Callable<V> {
    public abstract V call() throws Exception;
}

可以看出它是接口,提到接口就可以明白接口是灵活的,支持传入泛型参数。这个没什么,我们来重点介绍一下Future

Future

首先来看关于它的介绍
Future提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。

来看一下Future的代码

public abstract interface Future<V> {
    public abstract boolean cancel(boolean paramBoolean);

    public abstract boolean isCancelled();

    public abstract boolean isDone();

    public abstract V get() throws InterruptedException, ExecutionException;

    public abstract V get(long paramLong, TimeUnit paramTimeUnit)
            throws InterruptedException, ExecutionException, TimeoutException;
}

提供了五个方法

 

public abstract boolean cancel(boolean paramBoolean)

试图取消任务的执行(注意是试图),因为存在一些任务已完成、已取消或者因为某些原因无法取消的因素,存在着取消失败的可能性。

当canel方法起作用时,有两个情况:

1.任务未开始,则该任务将永远不会运行;

2.任务处于执行状态,paramBoolean表示是否采用中断的方式中断线程。

 

public abstract boolean isCancelled()

如果任务正常取消的,则返回true。

 

 

public abstract boolean isDone();

如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。

(注意如果调用isCanle方法,那么isDone将始终返回true).

 

public abstract V get() throws InterruptedException, ExecutionException;

重点到了!这是Future获取计算结果的方式之一,使用get方法。(注意这里返回的是Callable中的泛型)

 get方法取决于任务的状态(未开始,运行中,已完成),如果任务已经完成,那么get会立即返回或者抛出一个异常;

如果任务没有完成,那么get将阻塞知道任务完成。如果任务抛出了异常,那么get会将该异常封装成ExecutionException抛出。

 

public abstract V get(long paramLong, TimeUnit paramTimeUnit)
            throws InterruptedException, ExecutionException, TimeoutException;

如果需要在给定时间后获取计算结果,可以使用这个方法,如果超过给定时间之后没有得到计算结果,则抛出TimeoutException。(注意这里返回的是Callable中的泛型)  

 

如何使用

来看代码:

import java.util.concurrent.*;

public class Demo1 {

    public static void main(String args[]) throws Exception {
        // 1.先实例化任务对象
        ServiceTask task = new ServiceTask();
        // 2.实例化Executor框架中的线程池
        ExecutorService executor = Executors.newCachedThreadPool();
        // 3.使用submit方法将任务提交(返回的是一个Future)
        Future<Integer> result = executor.submit(task);
        // 4.记得关闭线程池
        executor.shutdown();
        System.out.println("正在执行任务");
        Thread.sleep(1000);
        // 5.打印最后的结果
        System.out.println("task运行结果为:" + result.get());
    }
}

/**
 * Callable的实现类
 */
class ServiceTask implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        Thread.sleep(2000);
        int result = 0;
        // 假设一个很庞大的计算
        for(int i=1;i<100;i++){
            for (int j=0;j<i;j++){
                result +=j;
            }
        }
        return result;
    }
}

运行结果:

接下来我们来试一下定时取结果:

还是在原来的代码上修改:

import java.util.concurrent.*;

public class Demo1 {

    public static void main(String args[]) throws Exception {
        // 1.先实例化任务对象
        ServiceTask task = new ServiceTask();
        // 2.实例化Executor框架中的线程池
        ExecutorService executor = Executors.newCachedThreadPool();
        // 3.使用submit方法将任务提交(返回的是一个Future)
        Future<Integer> result = executor.submit(task);
        // 4.记得关闭线程池
        executor.shutdown();
        System.out.println("正在执行任务");
        Thread.sleep(1000);
        // 5.设置定时一秒取结果
        System.out.println("task运行结果为:" + result.get(1,TimeUnit.MILLISECONDS));
    }
}

/**
 * Callable的实现类
 */
class ServiceTask implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        //这里睡眠2秒
        Thread.sleep(2000);
        int result = 0;
        // 假设一个很庞大的计算
        for(int i=1;i<100;i++){
            for (int j=0;j<i;j++){
                result +=j;
            }
        }
        return result;
    }
}

来提前猜想一下,首先设置了定时一秒之后取得结果,但是ServiceTask设置两秒的睡眠时间,理应取结果失败,看一下运行结果:

是的,如果在规定时间内无法取到结果,就会返回TimeoutException。

 

谈谈FutureTask

FutureTask是Future的实现类,它继承了RunnableFuture,RunnableFuture实际上继承了Runnable和Future接口。

来看一下使用如何FutureTask:

import java.util.concurrent.*;

public class FutureCallDemo2 {

    public static void main(String args[])throws  Exception{
        // 1.先实例化任务对象
        FutureTaskService task = new FutureTaskService();
        // 2.实例化Executor框架中的线程池
        ExecutorService excutor = Executors.newCachedThreadPool();
        // 3.直接new一个FutureTask
        FutureTask<Long> result = new FutureTask<Long>(task);
        // 4.提交任务
        excutor.submit(result);
        // 5.关闭线程池
        excutor.shutdown();
        System.out.println("主线程正在执行任务");
        System.out.println("task运行结果为:" + result.get());
    }
}

/**
 * 继承Callable接口
 */
class FutureTaskService implements Callable<Long> {

    @Override
    public Long call() throws Exception {
        Thread.sleep(3000);
        // 10的阶乘
        long sum = 1;
        for (int i = 1; i <= 10; i++) {
            sum = sum * i;
        }
        return sum;
    }
}

用法的话其实差不多。

 

总结:

Future和Callable可以实现异构任务,但是有很多值得考虑的地方。

比如一个类使用了两个任务,一个负责渲染页面,一个负责下载图像。

伪代码如下:

//通过获取图像
List<ImageData>ImageDataList = future.get();
for(ImageData data:ImageDataList ){
  //渲染页面
  renderPage(data);    
}

看似并行的执行任务,但是却存在着问题。如果说下载图像的速度远小于渲染页面的速度,那么最终的执行速度就和串行无异了。

所以只有当大量相互独立且同构的任务可以进行并发处理时,才能体现出将任务分到多个任务中带来的性能提升,考虑实际情况再选择使用会带来事半功倍的效果。

 

本文参考:

Java并发编程实战

个人博客网站 http://www.janti.cn
相关文章
|
3天前
|
Java 程序员
Java社招面试中的高频考点:Callable、Future与FutureTask详解
大家好,我是小米。本文主要讲解Java多线程编程中的三个重要概念:Callable、Future和FutureTask。它们在实际开发中帮助我们更灵活、高效地处理多线程任务,尤其适合社招面试场景。通过 Callable 可以定义有返回值且可能抛出异常的任务;Future 用于获取任务结果并提供取消和检查状态的功能;FutureTask 则结合了两者的优势,既可执行任务又可获取结果。掌握这些知识不仅能提升你的编程能力,还能让你在面试中脱颖而出。文中结合实例详细介绍了这三个概念的使用方法及其区别与联系。希望对大家有所帮助!
92 60
|
5月前
|
并行计算 Java 大数据
Callable和Future
Callable和Future
|
5月前
|
存储 Java
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
|
2月前
|
监控 Java 数据库连接
线程池在高并发下如何防止内存泄漏?
线程池在高并发下如何防止内存泄漏?
127 6
|
2月前
|
缓存 监控 Java
Java 线程池在高并发场景下有哪些优势和潜在问题?
Java 线程池在高并发场景下有哪些优势和潜在问题?
|
2月前
|
Java 数据库连接 API
Spring 框架的介绍(Java EE 学习笔记02)
Spring是一个由Rod Johnson开发的轻量级Java SE/EE一站式开源框架,旨在解决Java EE应用中的多种问题。它采用非侵入式设计,通过IoC和AOP技术简化了Java应用的开发流程,降低了组件间的耦合度,支持事务管理和多种框架的无缝集成,极大提升了开发效率和代码质量。Spring 5引入了响应式编程等新特性,进一步增强了框架的功能性和灵活性。
67 0
|
4月前
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(基础篇)
从Java环境的搭建到实际代码的编写,从基本用法的讲解到底层原理的剖析,深度解析Java基础知识。本文是《Java学习路线》专栏的起始文章,旨在提供一套完整的Java学习路线,覆盖Java基础知识、数据库、SSM/SpringBoot等框架、Redis/MQ等中间件、设计模式、架构设计、性能调优、源码解读、核心面试题等全面的知识点,并在未来不断更新和完善,帮助Java从业者在更短的时间内成长为高级开发。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(基础篇)
|
3月前
|
Java Linux
【网络】高并发场景处理:线程池和IO多路复用
【网络】高并发场景处理:线程池和IO多路复用
98 2
|
4月前
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)
本文是Java基础的进阶篇,对异常、集合、泛型、Java8新特性、I/O流等知识进行深入浅出的介绍,并附有对应的代码示例,重要的地方带有对性能、底层原理、源码的剖析。适合Java初学者。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)
|
3月前
|
Java 数据安全/隐私保护
java学习笔记(基础习题)
java学习笔记(基础习题)
53 0

热门文章

最新文章

  • 1
    Nginx实现高并发
    95
  • 2
    高并发场景下,到底先更新缓存还是先更新数据库?
    98
  • 3
    Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
    99
  • 4
    Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
    84
  • 5
    Java面试题:如何实现一个线程安全的单例模式,并确保其在高并发环境下的内存管理效率?如何使用CyclicBarrier来实现一个多阶段的数据处理任务,确保所有阶段的数据一致性?
    84
  • 6
    Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
    72
  • 7
    Java面试题:假设你正在开发一个Java后端服务,该服务需要处理高并发的用户请求,并且对内存使用效率有严格的要求,在多线程环境下,如何确保共享资源的线程安全?
    89
  • 8
    在Java中实现高并发的数据访问控制
    56
  • 9
    使用Java构建一个高并发的网络服务
    43
  • 10
    微服务06----Eureka注册中心,微服务的两大服务,订单服务和用户服务,订单服务需要远程调用我们的用,户服务,消费者,如果环境改变,硬编码问题就会随之产生,为了应对高并发,我们可能会部署成一个集
    67