JAVA 异步编程(异步,线程,线程池)一

目录

1.概念

1.1 线程和进程的区别

1.2 线程的五种状态

1.3 单线程,多线程,线程池

1.4 异步与多线程的概念

2. 实现异步的方式

2.1 方式1 裸线程(Thread)

2.1 方式2 线程池(Executor)

2.1.1 源码分析

2.1.2  线程池创建(Executors)

 2.1.3 阻塞主线程获取子线程返回值

1.线程池awaitTermination轮询

2.线程池future.get

3. CountDownLatch类的await(推荐)

4.线程池invokeAll 

5. ExecutorCompletionService(强烈推荐)

2.1.4 非阻塞主线程获取子线程返回值

1. Future接口:

2. CompletableFuture类:

2.3 方式3 ForkJoinPool

2.4 方式4 Spring的Async注解

4. 总结:


1.概念

1.1 线程和进程的区别

        进程:是一个动态的过程,是一个活动的实体,简单来说,一个应用程序的运行就可以看作是一个进程。可以说,进程中包含了多个可以同时运行的线程。

线程(Thread):是运行中实际任务的执行者,线程是操作系统能够进行运算调度的最小单位。它被包装在进程中,是进程中的实际运行单位。每个线程都有自己的程序计数器、堆栈和局部变量,但它们共享进程的代码和内存。

1.2 线程的五种状态

  1. 新建(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new Thread()

  2. 就绪(Runnable):也被称为“可执行”状态,当线程对象调用start()方法(启动线程)后,线程即进入就绪状态。处于就绪状态的线程,只是说明了该线程可以运行,但还没有真正运行,等待CPU分配时间片。

  3. 运行(Running):当CPU开始调度处于就绪状态的线程时,线程进入运行状态,真正开始执行线程代码。

  4. 阻塞(Blocked):线程在运行过程中可能因为各种原因导致无法继续执行,比如等待I/O操作结果,或者尝试获得一个同步监视器而失败,这时它就会进入阻塞状态。

  5. 死亡(Dead):线程执行完毕或者因异常退出run()方法后,线程就进入死亡状态。

1.3 单线程,多线程,线程池

单线程: 顾名思义是只有一条线程在执行任务,在工作中很难遇到。

多线程: 是创建多条线程同时执行任务。

线程池(ThreadPool): 是一种管理线程的机制,它能够复用线程,避免因频繁创建和销毁线程导致的性能问题。通过设置线程池的大小,可以有效管理线程的运行。

1.4 异步与多线程的概念

        异步和多线程并不是同一关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是调用者发送一个请求给被调用者,而调用者不用等待请求结果的返回,可以去做其他事。实现异步可以使用多线程或交给其他进程来处理。

2. 实现异步的方式

Util类:

public class Util {/*** sleep** @param milliseconds*/public static void mySleep(int milliseconds) {try {TimeUnit.MILLISECONDS.sleep(milliseconds);} catch (InterruptedException e) {}}/*** print log* @param message*/public static void printfLog(String message) {LocalDateTime localDateTime = LocalDateTime.now();String dateString = localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));System.out.println(String.format("%s - %s", dateString, message));}
}

2.1 方式1 裸线程(Thread)

        使用“原汁原味”的裸线程(Thread)。Java线程本质上被映射到操作系统线程,并且每个线程对象对应着一个计算机底层线程。

JVM管理着线程的生存期,而且只要你不需要线程间通讯,你也不需要关注线程调度。

每个线程有自己的栈空间,它占用了JVM进程空间的指定一部分。

线程的接口相当简明,你只需要提供一个Runnable,调用.start()开始计算。没有现成的API来结束线程,你需要自己来实现,通过类似boolean类型的标记来通讯。

    private static void threadMethod(List<Integer> list) {while (list.get(0) > 0) {Util.mySleep(Double.valueOf(Math.random() * 100).intValue());Integer result = list.get(0);result--;list.add(0, result);}}public static void threadTest() {Util.printfLog("主线程开始");//线程外的变量只读,值类型只能显示的生命final//这里正常应该会输出list result=0,但是这里因为时多个线程同时操作一个变量导致线程不安全,输出list result为负值List<Integer> list = new ArrayList<>(1);list.add(100);for (int i = 0; i < 100; i++) {Runnable runnable = new Runnable() {@Overridepublic void run() {threadMethod(list);Util.printfLog("子线程结束!currentName = " + Thread.currentThread().getName());}};Thread thread = new Thread(runnable);thread.start();}Util.printfLog("主线程结束");Util.mySleep(10000);Util.printfLog("list result=" + list.get(0));}

这里暂时没考虑线程安全行,所以list result可能会出现负值。

2.1 方式2 线程池(Executor)

       2.1.1 源码分析

 Executor一个接口,它通过一系列抽象类,接口等最终生成了线程池ThreadPoolExecutor。

它的包在java.util.concurrent。线程池的底层实现也是通过一系列的操作,通过Thread创建单独的线程。

ExecutorService.submit->AbstractExecutorService.submit->ThreadPoolExecutor.execute->
ThreadPoolExecutor.addWorker->Worker构造函数->DefaultThreadFactory.newThread

也可以使用工具类Executors创建线程池,底层也是通过创建ThreadPoolExecutor创建线程池。

ThreadPoolExecutor的主要方法:

方法名描述
submit

Callable<T>的实现类,创建带返回值的线程

Runnable的实现类,创建不带返回值的线程

shutdown优雅的终止线程。线程池中的线程不会立即结束,等线程池中没有在运行的线程才终止后台的线程池。
shutdownNow直接终止线程。不管线程池中有没有在运行的线程,直接将后台的线程池终止。

2.1.2  线程池创建(Executors)

线程池Executors工具类的主要方法:

方法名描述
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。

ScheduledExecutorService的方法:

        schedule:定时到达时间间隔执行。
        scheduleAtFixedRate:周期,从上一个任务开始运行就开始计时
        scheduleWithFixedDelay:周期,从上一次任务运行结束开始计时

    public static void main(String[] args) throws IOException {System.out.println("主线程【开始】");executorsNoWait();System.out.println("主线程【结束】");System.in.read();
}    /*** 线程直接提交,提交完成之后直接直接回到主进程*/private static void executorsNoWait() {ExecutorService executorService = Executors.newFixedThreadPool(10);executorService.submit(() -> {threadMethod(5, "线程--1");return "第一线程";});executorService.submit(() -> {threadMethod(2, "线程--2");return "第二线程";});// 线程池没shutdwon,后台运行executorService.shutdown();}private static void threadMethod(int num, String name) {for (int i = 0; i < num; i++) {Util.mySleep(1000);Util.printfLog(String.format("%s", name));}}

这种方式创建只提交线程到线程池,不阻塞主线程,不获取线程的返回值。通过代码运行结果,主线程已经执行完成,但是线程池中的子线程还没结束所以没有获取结果就不会阻塞主线程的运行。 只有当线程池中的没有runing的线程,线程池才会shutdown。

 2.1.3 阻塞主线程获取子线程返回值

        这种方式就是主线程提交子线程后,子线程异步运行。然后通过阻塞主线程,等待子线程都运行结束,再获取子线程的结果

1.线程池awaitTermination轮询

这种方式只能通过future.get方式获取返回值,只能等全部子线程执行完成才能获取,通过future.get返回值。

  /*** 线程池等待,阻塞回到主进程--awaitTermination轮训*/private static void executorsAwaitTermination() {ExecutorService executorService = Executors.newFixedThreadPool(10);Future<String> future = executorService.submit(() -> {threadMethod(5, "线程--1");return "第一线程";});Future<String> future2 = executorService.submit(() -> {threadMethod(2, "线程--2");return "第二线程";});executorService.shutdown();try {while (!executorService.awaitTermination(1, TimeUnit.SECONDS)) {Util.printfLog("等待中");}} catch (Exception e) {}}

2.线程池future.get

直接使用future.get获取子线程的返回值,通过遍历集合中的future,以固定的顺序获取子线程返回值,只有当获取的子线程有返回值之后才继续循环获取下一个子线程的返回值。

 /*** 线程池等待,阻塞回到主进程--Future.get()*/private static void executorsFutureGet() {ExecutorService executorService = Executors.newFixedThreadPool(10);List<Future<String>> listTemp = new ArrayList<>();Future<String> future = executorService.submit(() -> {threadMethod(5, "线程--1");return "第一线程";});listTemp.add(future);Future<String> future2 = executorService.submit(() -> {threadMethod(2, "线程--2");return "第二线程";});listTemp.add(future2);for (Future<String> item : listTemp) {try {System.out.println(item.get());} catch (Exception e) {}}executorService.shutdown();}

3. CountDownLatch类的await(推荐)

通过await方法阻塞主线程等待全部子线程执行完成后,通过future.get获取子线程的返回值,这种方式比方式一更加优雅。

    /*** 线程池等待,阻塞回到主进程--CountDownLatch*/private static void executorsCountDownLatch() {ExecutorService executorService = Executors.newFixedThreadPool(10);CountDownLatch countDownLatch = new CountDownLatch(2);Future<String> future = executorService.submit(() -> {threadMethod(5, "线程--1");countDownLatch.countDown();return "第一线程";});Future<String> future2 = executorService.submit(() -> {threadMethod(2, "线程--2");countDownLatch.countDown();return "第二线程";});executorService.shutdown();try {countDownLatch.await();Util.printfLog(future2.get());Util.printfLog(future.get());} catch (Exception e) {}}

4.线程池invokeAll 

这种方式和方式二类似,也是通过future.get获取子线程返回值,只能通过遍历集合中的future,以固定的顺序获取子线程返回值。

/*** 线程池等待,阻塞回到主进程---使用invokeAll*/private static void executorsInvokeAll() {ExecutorService executorService = Executors.newFixedThreadPool(10);Callable<String> callable = () -> {threadMethod(5, "线程--1");return "第一线程";};Callable<String> callable2 = () -> {threadMethod(2, "线程--2");return "第二线程";};List<Callable<String>> list = Arrays.asList(callable, callable2);try {List<Future<String>> futureList = executorService.invokeAll(list);executorService.shutdown();Util.printfLog("获取结果中---");for (Future<String> item : futureList) {Util.printfLog(item.get());}} catch (Exception e) {}}

5. ExecutorCompletionService(强烈推荐)
  • 优雅的获取子线程返回值,只要任何子线程结束就有返回值。
  • ExecutorCompletionService内部管理者一个已完成任务的阻塞队列
  • ExecutorCompletionService引用了一个Executor, 用来执行任务
  • submit()方法最终会委托给内部的executor去执行任务
  • take/poll方法的工作都委托给内部的已完成任务阻塞队列
  • 如果阻塞队列中有已完成的任务, take方法就返回任务的结果, 否则阻塞等待任务完成。
/*** 线程池等待,阻塞回到主进程---使用ExecutorCompletionService*/private static void executorsExecutorCompletionService() {ExecutorService executorService = Executors.newFixedThreadPool(10);ExecutorCompletionService<String> executorCompletionService = new ExecutorCompletionService(executorService);executorCompletionService.submit(() -> {threadMethod(5, "线程--1");return "第一线程返回";});executorCompletionService.submit(() -> {threadMethod(2, "线程--2");return "第二线程返回";});executorService.shutdown();try {Util.printfLog(executorCompletionService.take().get());Util.printfLog(executorCompletionService.take().get());} catch (Exception e) {}}

2.1.4 非阻塞主线程获取子线程返回值

1. Future接口:

        JDK 5引入了Future模式。Future接口是Java多线程Future模式的实现,在java.util.concurrent包中,可以来进行异步计算。Future模式是多线程设计常用的一种设计模式。
        Future虽然可以实现获取异步执行结果的需求,但它没有提供通知的机制,我们无法得知Future什么时候完成,不是真正意义上的异步。
        使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方式都会使主线程也会被迫等待,耗费CPU的资源。

private static void futureGet() {try {ExecutorService executor = Executors.newFixedThreadPool(1);Future<Integer> future = executor.submit(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {Util.printfLog("===task start===");Util.mySleep(5000);Util.printfLog("===task finish===");return 3;}});executor.shutdown();//这里需要返回值时会阻塞主线程Integer result = future.get();Util.printfLog("线程返回值:" + result);} catch (Exception e) {throw new RuntimeException(e);}}

 

2. CompletableFuture类:

        CompletableFuture实现异步操作,加上对lambda的支持,可以说实现异步任务已经发挥到了极致。CompletableFuture弥补了Future模式的缺点。在异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理。

CompletableFuture的静态工厂方法:

方法名描述
runAsync(Runnable runnable)使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,异步操作无返回值
runAsync(Runnable runnable, Executor executor)使用指定的thread pool执行异步代码,异步操作无返回值
supplyAsync(Supplier<U> supplier)使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,异步操作有返回值
supplyAsync(Supplier<U> supplier, Executor executor)使用指定的thread pool执行异步代码,异步操作有返回值
 /*** 线程提交后,不阻塞主进程,* completableFuture子线程执行完后回调--thread pool执行异步代码*/private static void completableFutureThreadPool() {ExecutorService executorService = Executors.newFixedThreadPool(10);CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {threadMethod(5, "线程--1");return "第一线程";}, executorService);CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> {threadMethod(2, "线程--2");return "第二线程";}, executorService);executorService.shutdown();completableFuture.thenAccept((r) -> {Util.printfLog(r);});completableFuture2.thenAccept((r) -> {Util.printfLog(r);});}

2.3 方式3 ForkJoinPool

        Java 8中加入了并行流,从此我们有了一个并行处理集合的简单方法。它和lambda一起,构成了并发计算的一个强大工具。默认情况下是通过ForkJoinPool.commonPool()实现并行的。这个通用池由JVM来管理,并且被JVM进程内的所有线程共享。

    /*** 使用ForkJoinPool,异步发处理*/private static void forkJoinPool() {List<Supplier<String>> actionList = Arrays.asList(() -> {threadMethod(5, "线程--1");return "第一线程";},() -> {threadMethod(2, "线程--2");return "线程2返回";});List<String> threadResult = actionList.parallelStream().map(row -> row.get()).collect(Collectors.toList());Util.printfLog(threadResult.stream().collect(Collectors.joining(",")));}

2.4 方式4 Spring的Async注解

spring实现异步需要开启注解@EnableAsync,可以使用xml方式或者java code config的方式。
 (1)@Async 异步的方法

4. 总结:

        虽然Thread可以创建线程,但是线程的创建销毁不能很好的控制,就会导致资源耗尽的风险,所以线程资源尽量通过线程池提供,不在应用中自行显示的创建线程,一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。

        线程池的创建尽量不使用Executors,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。

参考:

Java 并发的四种风味:Thread、Executor、ForkJoin 和 Actor

java多线程并发之旅-28-Executor CompletionService ExecutorCompletionService 详解

Java8新的异步编程方式 CompletableFuture

java线程池ThreadPoolExecutor类使用详解

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

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

相关文章

南京邮电大学计算机考研考情分析!专业课均分127分!复试录取比例偏高近2:1!计算机类共录取543人!

南京邮电大学&#xff08;Nanjing University of Posts and Telecommunications&#xff09;&#xff0c;位于南京市&#xff0c;简称南邮&#xff08;NJUPT&#xff09;&#xff0c;是教育部、工业和信息化部、国家邮政局与江苏省共建高校&#xff0c;国家“双一流”建设高校&…

软考中级科目包含哪些?应该考哪个?

软考中级包含5个专业方向&#xff0c;分别是&#xff1a;计算机软件、计算机网络、计算机应用技术、信息系统、信息服务。这5个方向又对应15个软考中级科目。 信息系统包括&#xff1a;系统集成项目管理工程师、信息系统监理师、信息安全工程师、数据库系统工程师、信息系统管…

C# 中IEnumerable与IQuerable的区别

目的 详细理清IEnumerator、IEnumerable、IQuerable三个接口之间的联系与区别 继承关系&#xff1a;IEnumerator->IEnumerable->IQuerable IEnumerator&#xff1a;枚举器 包含了枚举器含有的方法&#xff0c;谁实现了IEnuemerator接口中的方法&#xff0c;就可以自定…

力扣Hot100之两数之和

解法一&#xff1a; 双层循环暴力求解&#xff0c;先在数组的一个位置定住然后在这个位置的后续位置进行判断&#xff0c;如果两个数加起来等于目标和那么就返回 class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:for i,num in enumerate(num…

Windows 系统利用 SSH 和 WSL2 子系统当服务器

由于最近组内需要将一台 Windows 系统的电脑 W A W_A WA​ 转成能通过 SSH 访问&#xff0c;并且能用 Linux 命令当服务器运行。忙活了一天&#xff0c;终于是把全部东西弄通了。 安装 SSH 首先就是 W A W_A WA​ 先要安装 OpenSSH 服务&#xff0c;直接按照下面的教程安装…

HCIE是什么等级的证书?

HCIE&#xff08;华为认证互联网专家&#xff0c;Huawei Certified Internetwork Expert&#xff09;是华为认证体系中的最高等级证书。它要求考生具备在复杂网络环境中规划、设计、部署、运维和优化网络的能力。HCIE认证是华为认证体系中最具挑战性和含金量的认证之一&#xf…

RocketMQ实现分布式事务

RocketMQ的分布式事务消息功能&#xff0c;在普通消息基础上&#xff0c;支持二阶段的提交。将二阶段提交和本地事务绑定&#xff0c;实现全局提交结果的一致性。 1、生产者将消息发送至RocketMQ服务端。 2、RocketMQ服务端将消息持久化成功之后&#xff0c;向生产者返回Ack确…

NDK R25b 交叉编译FFMpeg4,项目集成,附库下载地址

1.准备工作 文件下载&#xff1a; NDK R25b下载地址&#xff1a;Android NDK历史版本下载网址 - 君*邪 - 博客园 (cnblogs.com) FFmpeg4.4.4 下载地址&#xff1a;https://ffmpeg.org/releases/ffmpeg-4.4.4.tar.xz 环境配置&#xff1a; 本次编译环境是在PC虚拟机中使用U…

普通人还有必要学习 Python 之类的编程语言吗?

在开始前分享一些编程的资料需要的同学评论888即可拿走 是我根据网友给的问题精心整理的对于编程的重要性&#xff0c;这里就不详谈了。 未来&#xff0c;我们和机器的交流会越来越多&#xff0c;编程可以简单看作是和机器对话并分发给机器任务。机器不仅越来越强大&#xff0…

C# —— CRC16 算法

CRC16:即循环冗余校验码。数据通信当中一种常用的查错校验码 其特征信息字段和校验字段的长度可以是任意选定的&#xff0c;对数据进行指定多项式计算 并且将得到的结果附加在帧的后面&#xff0c;接受的设备也执行类似的算法&#xff0c;以保证数据传输的正确性和完整性 crc…

鸿蒙语言基础类库:【@system.configuration (应用配置)】

应用配置 说明&#xff1a; 从API Version 7 开始&#xff0c;该接口不再维护&#xff0c;推荐使用新接口[ohos.i18n]和[ohos.intl]。本模块首批接口从API version 3开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import configurati…

云服务器实际内存与购买不足量问题

君衍 一、本篇缘由二、问题研究1、dmidecode2、dmesg | grep -i memory 三、kdump四、解决方案1、卸载kdump-tools2、清理依赖包3、修改配置文件4、重新生成配置文件5、重启服务器6、再次查看 一、本篇缘由 本篇由于最近买了云服务器&#xff0c;之前基本在本地使用VMware进行虚…

web自动化测试selenium的基本使用

目录 初始化浏览器并打开网页 定位网页元素 定位的方法 模拟键盘操作 模拟鼠标操作 xpath方法 xpath结点 路径表达式 轴 selenium是一个很流行的自动化测试的库&#xff0c;主要用于模拟浏览器的运行&#xff0c;是web应用测试的工具。 在使用selenium时&#xff0c;…

(二)高并发压力测试调优篇——caffeine本地缓存调优

前言 在上一节内容中我们主要介绍了高并发请求下&#xff0c;mysql数据库的调优&#xff0c;其调优的主要原理是尽量减少数据库的IO操作&#xff0c;从而提高服务器的访问性能。在此基础上&#xff0c;本节内容是关于如何利用缓存&#xff0c;提高系统的并发访问能力。我们会首…

Linux部署禅道(无脑复制版)

目录 环境部署1、下载&#xff0c;解压2、启动3、设置开机自启 登录禅道登录数据库1、设置账号2、网页登录数据库 环境 Linux系统 Centos7 《Linux一键安装包安装禅道》视频链接&#xff1a; https://www.zentao.net/zentao-install/zentao-linux-install-80523.html 部署 …

谢启昆:乾隆年间的清廉典范与学术巨擘

谢启昆&#xff0c;一位生活在清朝乾隆年间的杰出人物&#xff0c;以其清廉的政绩和卓越的学术成就&#xff0c;成为后世效仿的典范。他的画像中&#xff0c;目光如炬&#xff0c;透露出坚毅与智慧的光芒&#xff0c;仿佛在诉说着他不平凡的一生。 谢启昆出生在一个书香门第&am…

多元统计分析概述

目录 1. 多元回归分析 2. 主成分分析&#xff08;PCA&#xff09; 3. 因子分析 4. 判别分析 5. 聚类分析 6. 多维尺度分析&#xff08;MDS&#xff09; 结论 多元统计分析是一组用于分析多个变量之间关系的统计方法。它广泛应用于各个领域&#xff0c;如市场研究、生物医…

PHP webshell 免杀方法

本文介绍php类webshell简单的免杀方法&#xff0c;总结不一定全面&#xff0c;仅供读者参考。 webshell通常可分为一句话木马&#xff0c;小马&#xff0c;大马&#xff0c;内存马。 一句话木马是最简单也是最常见的webshell形式&#xff0c;这种木马体积小&#xff0c;隐蔽较…

a newer or same version is present nvidia解决方案

安装时候出现a newer or same version is present nvidia 或者Night Visual Editor 失败&#xff0c;把显卡驱动卸载掉&#xff0c;打开service.mtc 服务控制面板&#xff0c;把nvidia开头的服务全停掉&#xff0c;重新启动cuda安装程序选择自定义安装 vse visual studio相关的…