【JUC编程】-多线程和CompletableFuture的使用

多线程编程

引言

为什么使用多线程?

  1. 最直接的就是提升程序性能,使用多线程可以充分利用硬件资源,同时执行多个任务,从而提高程序的整体性能。通过并行执行任务,可以将工作负载分布到多个线程上,从而更有效地利用 CPU 资源。
  2. 提高响应性:可以将长时间处理的请求放在后台另一个线程进行处理,不妨碍主线程的用户执行其他的请求
  3. 实现并发编程:现在的工作中,多线程是并发编程的一种重要方式。利用好多线程机制可以大大提高系统整体的并发能力以及性能。

创建多线程的方式

从实现上来说,Java提供了三种创建线程的方式,但从原理上来看,其实只有一种方式,我们先从实现上来简单介绍一下这三种方式

继承Thread类

直接创建一个ThreadTest的实例,调用它的start()方法就可以创建一个线程了

class ThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

实现Runnable接口

如果只是简单的实现了Runnable接口,它与线程并没有任何关系,只是相当于创建了一个线程执行的任务类而已,要想真正的创建线程,还是需要创建一个Thread对象,把RunnableTest实例作为构造方法的入参

1.2 实现Runnable接口
如果只是简单的实现了Runnable接口,它与线程并没有任何关系,只是相当于创建了一个线程执行的任务类而已,要想真正的创建线程,还是需要创建一个Thread对象,把RunnableTest实例作为构造方法的入参

class RunnableTest implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

public class CreateThreadTest {
	public static void main(String[] args) {
        RunnableTest runnableTest = new RunnableTest();
        Thread thread = new Thread(runnableTest);
        thread.start();
    }
}

实现Callable接口

与Runnable很相似,它相当于也是也个任务的实现类,需要结合线程池的submit()方法才能使用,但与Runnable最本质的区别是,Callable的call()方法可以有返回值

class CallableTest implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        return ThreadLocalRandom.current().nextInt();
    }
}

public class CreateThreadTest {
    public static void main(String[] args) {
        CallableTest callableTest = new CallableTest();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Future<Integer> future = executorService.submit(callableTest);
    }
}
Callable和Runnable的区别

Callable的call方法可以有返回值,可以声明抛出异常。和 Callable配合的有一个Future类,通过Future可以了解任务执行情况,或者取消任务的执行,还可获取任务执行的结果,这些功能都是Runnable做不到的,Callable 的功能要比Runnable强大。

@FunctionalInterface
public interface Runnable {
    // 没有返回值
    public abstract void run();
}

@FunctionalInterface
public interface Callable<V> {
    // 有返回值
    V call() throws Exception;
}

Lambda表达式

这种方式与第二种方式其实是一样的,只是写法比较简洁明了

Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()

线程的实现原理

这三种创建线程的方式,第一种是直接通过继承Thread进行实现,第二种是通过实现Runnable接口,然后将类作为创建Thread类的入参,其实也是通过实现创建Thread类进行实现。

现在看第三种Callable的方式到底是怎么实现的,他是通过实现Callable接口,然后使用submit进行执行,这里我们通过debug这个方法,最后发现其实也是通过创建的Thread进行实现。

在这里插入图片描述

**总结:**所以最后我们发现三种方式其实都是创建Thread类进行实现

Future&FutureTask

我们一共有三种创建线程的方式,继承Thread和实现Runnable接口都是没有返回值的,所以我们不知道线程的执行状态,不能获取执行完成的一个结果。所以这时就需要Callable来解决上面的问题,通过CallableFuture能够获得执行的结果。

具体使用

public class CompletableFutureTest {
    private static ThreadPoolExecutor executor;
    static {
        executor = new ThreadPoolExecutor(10, 10, 100, TimeUnit.HOURS, new ArrayBlockingQueue<>(100), new ThreadFactory() {
            private int count = 0;
            @Override
            public Thread newThread(Runnable r) {
                count++;
                System.out.printf("CustomerThread- %d :", count);
                return new Thread(r, "CustomerThread-" + count);
            }
        });
    }

    static class CallableTest implements Callable<String>{
        @Override
        public String call() throws Exception {
            Thread.sleep(1000);
            System.out.println("线程开始运行");
            return "返回值";
        }
    }

    public static void main(String[] args) throws Exception{
        CallableTest test = new CallableTest();
        FutureTask<String> futureTask = new FutureTask<>(test);
        executor.submit(futureTask);
        System.out.println(futureTask.get());
        executor.shutdown();
    }
}

submit方法

在该方法中,我们传入的是FutureTask类型的,结果把参数转成了RunnableFuture,任务执行依然是execute()方法

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

这里的Runnable其实是FutureTask的父类

在这里插入图片描述

当我们使用Callable类的子类作为参数时候,其实也是转换为RunnableFuture,然后使用excute进行执行。

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

Future到FutureTask类

Future其实就是定义了一组接口,Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果

在这里插入图片描述

FutureTask实现了这个接口,同时还实现了Runnalbe接口,这样FutureTask就相当于是消费者和生产者的桥梁了,消费者可以通过FutureTask存储任务的执行结果,跟新任务的状态:未开始、处理中、已完成、已取消等等。而任务的生产者可以拿到FutureTask被转型为Future接口,可以阻塞式的获取处理结果,非阻塞式获取任务处理状态

**总结:**FutureTask既可以被当做Runnable放入Excutor来执行,也可以被当做Future来获取Callable的返回结果。

Future

注意事项
  • 当 for 循环批量获取Future的结果时容易 block,get 方法调用时应使用 timeout 限制
    • 因为我们可能会有耗时的任务,后面的任务只能等前面耗时的任务完成以后才能获取结果,所以有时会卡住
  • Future 的生命周期不能后退。一旦完成了任务,它就永久停在了“已完成”的状态,不能从头再来
局限性

从本质上说,Future表示一个异步计算的结果。它提供了isDone()来检测计算是否已经完成,并且在计算结束后,可以通过get()方法来获取计算结果。在异步计算中,Future确实是个非常优秀的接口。但是,它的本身也确实存在着许多限制:

  • 并发执行多任务:Future只提供了get()方法来获取结果,并且是阻塞的。所以,除了等待你别无他法;
  • 无法对多个任务进行链式调用:如果你希望在计算任务完成后执行特定动作,比如发邮件,但Future却没有提供这样的能力;
  • 无法组合多个任务:如果你运行了10个任务,并期望在它们全部执行结束后执行特定动作,那么在Future中这是无能为力的;
  • 没有异常处理:Future接口中没有关于异常处理的方法;

而这些局限性CompletionServiceCompletableFuture都解决了。

CompletionService

引言

CompletionService是一个为了解决我们并发执行多个线程的任务的时候,能够及时获取已经完成任务的结果而创建的一个抽象的接口类,我们一般是使用的他的实现类ExecutorCompletionService

使用

具体的使用规则还有方法的作用博客链接

使用场景

  1. 当需要批量提交异步任务的时候建议使用CompletionService。CompletionService将线程池Executor和阻塞队列BlockingQueue的功能融合在了一起,能够让批量异步任务的管理更简单。
  2. CompletionService能够让异步任务的执行结果有序化。先执行完的先进入阻塞队列,利用这个特性,你可以轻松实现后续处理的有序性,避免无谓的等待,同时还可以快速实现诸如Forking Cluster这样的需求。
  3. 线程池隔离。CompletionService支持自己创建线程池,这种隔离性能避免几个特别耗时的任务拖垮整个应用的风险。

CompletableFuture

引言

我们使用CompletionService能够解决多个线程并发执行时,获取执行结果的返回值时的阻塞问题。但是假如我们并发执行的多线程任务需要遵循一定的规则,或者执行的顺序时候,CompletionService就不能满足我们的需求了。

所以CompletableFuture其实是对Future进行扩展,弥补了Future的局限性,同时CompletableFuture实现了对任务编排的能力

在以往,虽然通过**CountDownLatch**等工具类也可以实现任务的编排,但需要复杂的逻辑处理,不仅耗费精力且难以维护。

在这里插入图片描述

更加详细介绍博客

继承结构

CompletionStage接口定义了任务编排的方法,执行某一阶段,可以向下执行后续阶段。异步执行的,默认线程池是ForkJoinPool.commonPool(),但为了业务之间互不影响,且便于定位问题,强烈推荐使用自定义线程池

在这里插入图片描述

任务的异步回调

在这里插入图片描述

多个任务组合处理

在这里插入图片描述

注意点

Future需要获取返回值,才能获取异常信息
ExecutorService executorService = new ThreadPoolExecutor(5, 10, 5L,
    TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
      int a = 0;
      int b = 666;
      int c = b / a;
      return true;
   },executorService).thenAccept(System.out::println);
   
 //如果不加 get()方法这一行,看不到异常信息
 //future.get();

Future需要获取返回值,才能获取到异常信息。如果不加 get()/join()方法,看不到异常信息。小伙伴们使用的时候,注意一下哈,考虑是否加try…catch…或者使用exceptionally方法。

CompletableFuture的get()方法是阻塞的。

CompletableFuture的get()方法是阻塞的,如果使用它来获取异步调用的返回值,需要添加超时时间~

csharp复制代码//反例
 CompletableFuture.get();
//正例
CompletableFuture.get(5, TimeUnit.SECONDS);
默认线程池的注意点

CompletableFuture代码中又使用了默认的线程池,处理的线程个数是电脑CPU核数-1。在大量请求过来的时候,处理逻辑复杂的话,响应会很慢。一般建议使用自定义线程池,优化线程池配置参数。

自定义线程池时,注意饱和策略

CompletableFuture的get()方法是阻塞的,我们一般建议使用future.get(3, TimeUnit.SECONDS)。并且一般建议使用自定义线程池。

但是如果线程池拒绝策略是DiscardPolicy或者DiscardOldestPolicy,当线程池饱和时,会直接丢弃任务,不会抛弃异常。因此建议,CompletableFuture线程池策略最好使用AbortPolicy,然后耗时的异步线程,做好线程池隔离哈。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/651845.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【蓝桥杯——物联网设计与开发】拓展模块2 - 电位器模块

一、电位器模块 &#xff08;1&#xff09;资源介绍 &#x1f505;原理图 蓝桥杯物联网竞赛实训平台提供了一个拓展接口 CN2&#xff0c;所有拓展模块均可直接安装在 Lora 终端上使用&#xff1b; 图1 拓展接口 电位器模块电路原理图如下所示&#xff1a; 图2 …

通用代码生成器应用场景三,遗留项目反向工程

通用代码生成器应用场景三&#xff0c;遗留项目反向工程 如果您有一个遗留项目&#xff0c;要重新开发&#xff0c;或者源代码遗失&#xff0c;或者需要重新开发&#xff0c;但是希望复用原来的数据&#xff0c;并加快开发。 如果您的项目是通用代码生成器生成的&#xff0c;…

无线蓝牙耳机品牌推荐:倍思M2s Pro,让旅途更添乐趣

随着端午节的临近,许多人开始规划起出游计划。出游除了要做好行程安排,还需准备一些实用的物品来提升旅途的舒适度。特别是在高铁等长途旅行中,一款优质的降噪蓝牙耳机无疑是消磨时光、享受音乐的绝佳选择。那么,在众多的无线蓝牙耳机品牌中,有哪些值得推荐的呢?今天,我们就来…

IT廉连看——UniApp——事件绑定

IT廉连看——UniApp——事件绑定 这是我们上节课最终的样式&#xff1b; 一、现在我有这样一个需求&#xff0c;当我点击“生在国旗下&#xff0c;长在春风里”它的颜色由红色变为蓝色&#xff0c;该怎么操作&#xff1f; 这时候我们需要一个事件的绑定&#xff0c;绑定一个单…

指纹识别经典图书、开源算法库、开源数据库

目录 1. 指纹识别书籍 1.1《精通Visual C指纹模式识别系统算法及实现》 1.2《Handbook of Fingerprint Recognition》 2. 指纹识别开源算法库 2.1 Hands on Fingerprint Recognition with OpenCV and Python 2.2 NIST Biometric Image Software (NBIS) 3. 指纹识别开源数…

react-native 默认停用 flipper 通知

react-native 0.74 默认停用 flipper &#xff0c;但仍然可以手动安装 flipper 官方声明文档 英语好的可以直接阅读。 integration with React Native will no longer be enabled 原因 增加编译时间有时候会有连接问题升级会导致不能使用 之后调试推荐 我们建议团队使用 A…

谷粒商城实战(029 业务-订单支付模块-支付宝支付2)

Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强 总时长 104:45:00 共408P 此文章包含第305p-第p310的内容 代码编写 前端代码 这里使用的是jsp 在这里引用之前配置的各种支付信息 在AlipayConfig.java里 这里是调用阿里巴巴写…

单片机的内存映射和重映射

内存映射 在单片机内&#xff0c;不管是RAM还是ROM还是寄存器&#xff0c;他们都是真实存在的物理存储器&#xff0c;为了方便操作&#xff0c;单片机会给每一个存储单元分配地址&#xff0c;这就叫做内存映射。 单片机的内存映射是指将外部设备或外部存储器映射到单片…

​测斜仪数据处理软件-MCU自动测量单元的重要性及应用

随着科技的快速发展&#xff0c;测斜仪数据处理软件在多个领域&#xff0c;如土木工程、地质学、环境科学等&#xff0c;扮演着越来越重要的角色。本文旨在探讨测斜仪数据处理软件-MCU自动测量单元的重要性、应用及其发展趋势。 一、MCU自动测量单元处理测斜仪数据的重要性 测斜…

快速上手 HuggingFace

HuggingFace HuggingFace 是类似于 GitHub 的社区&#xff0c;它主要提供各种的模型的使用&#xff0c;和 github 不同的是&#xff0c;HuggingFace 同时提供了一套框架&#xff0c;进行模型推理&#xff0c;模型训练、和模型库文件的管理等等。本文将介绍&#xff0c;如何快速…

用源码建站可能涉及知产侵权,建站的注意!

近日普推知产老杨看到央视报道一家公司用了某建站源码涉及知产侵权&#xff0c;起诉了全国八千多家公司&#xff0c;某梦自从创始人因病转给某公司后&#xff0c;也在大量起诉用其建站代码公司侵权&#xff0c;他们也都是申请了相关的著作权。 有的中小企业在运营中会涉及建站…

在React中使用Sass实现Css样式管理-10

0. 什么是Sass Sass(Syntactically Awesome Stylesheets)是一个 CSS 预处理器&#xff0c;是 CSS 扩展语言&#xff0c;可以帮助我们减少 CSS 重复的代码&#xff0c;节省开发时间&#xff1a; Sass 引入合理的样式复用机制&#xff0c;可以节约很多时间来重复。支持变量和函…

基于形态学滤波的心电信号ECG处理(MATLAB 2021B)

数学形态学简称形态学&#xff0c;在数学意义上&#xff0c;其基于集合理论、积分几何和网格代数&#xff0c;是一门严格建立在数学基础之上的学科&#xff0c;着重用来研究图像的几何结构和形状&#xff0c;因而称之为形态学。其基本思想是用结构元素对待分析图像进行“探测”…

【设计模式】创建型-工厂方法模式

前言 工厂方法模式是一种经典的创建型设计模式&#xff0c;它提供了一种灵活的方式来创建对象实例。通过本文&#xff0c;我们将深入探讨工厂方法模式的概念、结构和应用。 一、什么是工厂方法模式 工厂方法模式是一种创建型设计模式&#xff0c;旨在解决对象的创建过程和客…

【DevOps】Elasticsearch在Ubuntu 20.04上的安装与配置:详细指南

目录 一、ES 简介 1、核心概念 2、工作原理 3、 优势 二、ES 在 Ubuntu 20.04 上的安装 1、安装 Java 2、下载 ES 安装包 3、创建 ES 用户 4 、解压安装包 5、 配置 ES 6、 启动 ES 7、验证安装 三、ES 常用命令 1、创建索引 2、 插入文档 3、查询文档 四、ES…

操作系统 - 输入/输出(I/O)管理

输入/输出(I/O)管理 考纲内容 I/O管理基础 设备&#xff1a;设备的基本概念&#xff0c;设备的分类&#xff0c;I/O接口 I/O控制方式&#xff1a;轮询方式&#xff0c;中断方式&#xff0c;DMA方式 I/O软件层次结构&#xff1a;中断处理程序&#xff0c;驱动程序&#xff0c;…

VM中Ubuntu16.04的下载以及ROS—kinetic的版本下载

一、Ubuntu镜像地址 转载备份一下&#xff1b; 官方下载地址&#xff08;不推荐&#xff09; https://www.ubuntu.com/downloadhttps://www.ubuntu.com/download 中科大源 Index of /ubuntu-releases/16.04/http://mirrors.ustc.edu.cn/ubuntu-releases/16.04/ 阿里云开…

使用 Django ORM 进行数据库操作

文章目录 创建Django项目和应用定义模型查询数据更新和删除数据总结与进阶聚合和注解跨模型查询原始SQL查询 Django是一个流行的Web应用程序框架&#xff0c;它提供了一个强大且易于使用的对象关系映射&#xff08;ORM&#xff09;工具&#xff0c;用于与数据库进行交互。在本文…

0基础认识C语言(理论知识)

为了给0基础一个舒服的学习路径&#xff0c;就有了这个专栏希望带大家一起进步。 话不多说&#xff0c;开始正题。 一、C语言的一段小历史 C语言的设计要追溯到20世纪60年代末和70年代初&#xff0c;在那个时代美国有这么一号人叫做丹尼斯.里奇&#xff0c;他和同事肯.汤普逊…

学习编程对英语要求高吗?

学习编程并不一定需要高深的英语水平。我这里有一套编程入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习编程&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;私信22&#xff0c;我在后台发给你。 虽然一些编程资源和文档可能…