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

多线程编程

引言

为什么使用多线程?

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

创建多线程的方式

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

继承Thread类

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

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

实现Runnable接口

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

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

class RunnableTest implements Runnable{@Overridepublic 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>{@Overridepublic 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;@Overridepublic Thread newThread(Runnable r) {count++;System.out.printf("CustomerThread- %d :", count);return new Thread(r, "CustomerThread-" + count);}});}static class CallableTest implements Callable<String>{@Overridepublic 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,然后耗时的异步线程,做好线程池隔离哈。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第八大奇迹

目录 题目描述 输入描述 输出描述 输入输出样例 示例 输入 输出 运行限制 原题链接 代码思路 题目描述 在一条 R 河流域&#xff0c;繁衍着一个古老的名族 Z。他们世代沿河而居&#xff0c;也在河边发展出了璀璨的文明。 Z 族在 R 河沿岸修建了很多建筑&#xff0c…

java如何向数组中插入元素

java的数组是不可改变的&#xff0c;因此如果要向数组中插入新的元素&#xff0c;需要新建一个数组&#xff0c;新的数组元素个数减去老数组元素个数的差大于等于要插入新的元素数量。 假如说要插入一个数组元素&#xff0c;需要把新元素插入到中间&#xff0c;把新的数组分为…

Vue组件通讯⽗组件中通过 provide 来提供变量,然后在⼦组件中通过 inject 来注⼊变量例子

在Vue中&#xff0c;provide 和 inject 主要用于依赖注入&#xff0c;允许祖先组件向其所有子孙组件提供一个依赖&#xff0c;而不论组件层次有多深。这在开发高阶插件/组件库时特别有用。 以下是一个简单的例子&#xff0c;演示了如何在父组件中使用 provide 提供变量&#x…

软件测试面试题(八)

一&#xff1a;TestDirector有哪些功能&#xff0c;如何对软件测试过程进行管理&#xff1f; 需求管理 定义测试范围 定义需求树 描述需求树的功能点 测试计划 定义测试目标和测试策略 分解应用程序&#xff0c;建立测试计划树 确定每个功能点的测试方法 将每个功能点连接…

Ps 滤镜:消失点

Ps菜单&#xff1a;滤镜/消失点 Filter/Vanishing Point 快捷键&#xff1a;Ctrl Alt V 两条平行的铁轨或两排树木连线相交于很远很远的某一点&#xff0c;这点在透视图中叫做“消失点”&#xff0c;也称为“灭点”。 消失点 Vanishing Point滤镜主要用于在图像中处理具有透视…

C++入门3——类与对象(2)

1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。可是空类中真的什么都没有吗&#xff1f; 其实并不是的&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;用户没有显式实现&#xf…

libmodbus开发库介绍

目录 功能概要源码获取源码内容结构源码与移植 功能概要 libmodbus是一个免费的跨平台支持RTU和TCP的Modbus库&#xff0c;遵循LGPL V2.1协议。libmodbus支持Linux、Mac Os X、FreeBSD、QNX和Windows等操作系统。libmodbus可以向符合Modbus协议的设备发送和接收数据&#xff0…

vector的reverse和resize区别

一 代码 #include "stdafx.h" #include <iostream> #include <vector> using namespace std;class TEST{ public:TEST(){std::cout << "construct t" << std::endl;} };int main() {std::cout << "hello,world" …

《Python侦探手册:用正则表达式破译文本密码》

在这个信息爆炸的时代&#xff0c;每个人都需要一本侦探手册。阿佑今天将带你深入Python的正则表达式世界&#xff0c;教你如何像侦探一样&#xff0c;用代码破解文本中的每一个谜题。从基础的字符匹配到复杂的数据清洗&#xff0c;每一个技巧都足以让你在文本处理的领域中成为…

【一站式学会Kotlin】第十三节:kotlin语言中的解构

作者介绍: 百度资深Android工程师T6,在百度任职7年半。 目前:成立赵小灰代码工作室,欢迎大家找我交流Android、微信小程序、鸿蒙项目。= 一:通俗易懂的人工智能教程:https://www.captainbed.cn/nefu/ 点一下,打开新世界的大门。 二:【一站式学会Kotlin】免费领取:作者…

SQLSyntaxErrorException: FUNCTION dbname.to_timestamp does not exist

由于MySQL数据库高版本&#xff08;如8.x&#xff09;中有to_timestamp(&#xff09;函数&#xff0c;低版本中&#xff08;如5.7.x&#xff09;没有这个函数&#xff0c;服务运行报错。 自己创建函数实现功能&#xff0c;创建语句如下&#xff1b; DELIMITER // CREATE FUN…

如何使用ChatGPT撰写短视频爆款文案

在这个快速发展的数字时代&#xff0c;短视频已经成为最受欢迎的娱乐和信息获取方式之一。对于内容创作者来说&#xff0c;如何制作出爆款短视频&#xff0c;吸引更多观众的注意力&#xff0c;是他们面临的一大挑战。文案&#xff0c;作为视频内容的灵魂&#xff0c;起着至关重…

ESP32 - Micropython ESP-IDF 双线教程 中断和定时器 (1)

ESP32 - Micropython ESP-IDF 双线教程 中断和定时器 ESP32中断ESP32定时器归纳ESP32 - Micropython 定时器示例代码代码介绍 ESP32 - IDF 定时器示例代码代码解释ESP32-IDF定时器使用介绍 ESP32中的中断和定时器是两种重要的硬件特性&#xff0c;它们在嵌入式系统开发中扮演着…

系统思考—战略沙盘推演咨询服务

今日与JSTO团队一起学习了《战略沙盘推演咨询服务》。通过沙盘体验&#xff0c;我深刻感受到组织与战略就像一张皮的正反两面。在转型过程中&#xff0c;即使战略非常明确&#xff0c;团队成员由于恐惧和顾虑&#xff0c;往往不愿意挑战新的业务&#xff0c;从而难以实现战略目…

VasDolly图形工具-Android多渠道打包福利

简介 基于腾讯VasDolly最新版本3.0.6的图形界面衍生版本&#xff0c;旨在更好的帮助开发者构建多渠道包 使用 下载并解压工具包&#xff0c;找到Startup脚本并双击启动图形界面&#xff08;注意&#xff1a;本地需安装java环境&#xff09; 渠道格式说明 txt文件&#xff…

音频链接抓取技术在Lua中的实现

前言 随着数字音乐的普及&#xff0c;越来越多的用户选择在线音乐平台来享受音乐。网易云音乐作为国内领先的音乐服务平台&#xff0c;不仅提供了丰富的音乐资源&#xff0c;还拥有独特的社交属性&#xff0c;吸引了大量的用户。在众多的音乐服务中&#xff0c;音频链接的抓取…

Qt | QTabBar 类(选项卡栏)

01、上节回顾 Qt | QStackedLayout 类(分组布局或栈布局)、QStackedWidget02、简介 1、QTabBar类直接继承自 QWidget。该类提供了一个选项卡栏,该类仅提供了一个选项卡, 并没有为每个选项卡提供相应的页面,因此要使选项卡栏实际可用,需要自行为每个选项卡设置需要显示的页…

【面试题】JavaScript基础高频面试(上)

1、简述JavaScript中map和foreach的区别&#xff1f; map和forEach都是JavaScript数组的迭代方法&#xff0c;但它们之间存在一些关键区别。 1. 返回值&#xff1a;map方法会返回一个新的数组&#xff0c;这个新数组是由原数组通过某个函数处理后的结果组成的。而forEach方法…

Ubuntu18.04 重装/升级 eigen 教程

目录 一、Eigen 1.1 ubuntu 查看 eigen 版本 1.2 卸载 老版本 eigen 二、安装 eigen 3.4.0 2.1 配置安装 2.2 查看版本 一、Eigen 1.1 ubuntu 查看 eigen 版本 $ dpkg -l | grep eigen1.2 卸载 老版本 eigen sudo updatedb locate eigen3会获得一堆输出&#xff0c;其…

springboot整合Kafka的快速使用教程

目录 一、引入Kafka的依赖 二、配置Kafka 三、创建主题 1、自动创建(不推荐) 2、手动动创建 四、生产者代码 五、消费者代码 六、常用的KafKa的命令 Kafka是一个高性能、分布式的消息发布-订阅系统&#xff0c;被广泛应用于大数据处理、实时日志分析等场景。Spring B…