Java多线程----创建线程、线程池ExecutorService、异步编排

文章目录

  • 创建线程的四种方式
    • 方式一、继承Thread
    • 方式二、自定义实现Runnable接口
    • 方式三、Thread + FutureTask + Callable返回值
    • 方式四、线程池ThreadPoolExecutor
  • 线程池的简单介绍
  • 通过ThreadPoolExecutor创建自定义线程池
  • ThreadPoolExecutor创建线程池的7大参数
  • 线程池处理任务的执行流程
  • 四种RejectedExecutionHandler策略
    • AbortPolicy,丢弃并抛出异常
    • DiscardPolicy,静默丢弃什么也不做
    • DiscardOldestPolicy,丢弃最老后当前任务重新进池
    • CallerRunsPolicy,同步调用run()执行任务
  • Executors中四种常用的线程池
    • newCachedThreadPool
    • newFixedThreadPool
    • newScheduledThreadPool
    • newSingleThreadExecutor
  • CompletableFuture异步编排

创建线程的四种方式

通过方式一、方式二、方式三创建线程,都离不开Thread类以及它的start()来启动线程。所以它们和Thread类息息相关。创建线程本质就是new Thread();只是Thread类提供了多种不同入参的构造方法;
如下图所示;其中Runnable target类型的入参,衍生出两种创建线程的方式

  • 自定义实现Runnable接口
  • 构建Runnable的实例对象FutureTask;通过FutureTask就扯出了Callable接口
    在这里插入图片描述

方式一、继承Thread

public class CreateThread {public static void main(String[] args) {System.out.println("start");// 创建线程Thread01 thread01 = new Thread01();// 开启线程thread01.start();System.out.println("end");}/*** 期望通过继承Thread的方式创建并开启一个线程**/static class Thread01 extends Thread {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "---running");}}
}	

方式二、自定义实现Runnable接口

自定义类实现Runnable接口;通过Thread(Runnable target)的方式创建线程

public class CreateThread {public static void main(String[] args) {System.out.println("start");// 方式1;继承Thread类// Thread01 thread01 = new Thread01();// thread01.start();// 方式2;实现Runnable接口new Thread(()->System.out.println(Thread.currentThread().getName() + "---running")).start();System.out.println("end");}
}

方式三、Thread + FutureTask + Callable返回值

也是通过Thread(Runnable target)的方式创建线程,只不过Runnable类型不需要自定义创建,使用现成的FutureTask
在这里插入图片描述
FutureTask的构造器如下:

  • FutureTask(Runnable,V):说实话至今还没有见过使用使用场景,确实鸡肋。不常用
  • FutureTask(Callable<V>):可以返回异步执行结果
    在这里插入图片描述
    public static void main(String[] args) throws ExecutionException, InterruptedException {System.out.println("start");// 构建FutureTask对象,实现Callable接口FutureTask<Integer> futureTask = new FutureTask<>(() -> {System.out.println(Thread.currentThread().getName() + "---running");// 返回异步执行结果return 10 / 2;});Thread thread = new Thread(futureTask);thread.start();// get()阻塞等待,获取异步返回值Integer result = futureTask.get();System.out.println("result = " + result);System.out.println("end");}

方式四、线程池ThreadPoolExecutor

创建ThreadPoolExecutor自定义线程池,然后通过execute()向线程池中提交任务
在这里插入图片描述

public class ThreadPool {public static final ThreadPoolExecutor threadPool;static{threadPool = new ThreadPoolExecutor(5,20,30,TimeUnit.SECONDS,new LinkedBlockingQueue<>(50),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());}public static void main(String[] args) {threadPool.execute(()->{int i = 10 / 1;System.out.println(Thread.currentThread().getName() + "running....");});}}

线程池的简单介绍

Java中线程池就是Executor或者ExecutorService对象实例

之前通过new Thread()的方式创建线程存在以下几个问题:

  • 不能重复利用线程,有多少任务就创建多少个线程
  • 如果需要处理大量任务,就需要频繁地创建和销毁线程会浪费时间和效率
  • 如果同一时刻存在大量的线程,那么线程之间还存在竞争资源,CPU上下切换等问题

线程池通过预先创建一定数量的线程,让这些线程处理来自任务队列中的任务,而不是频繁创建和销毁线程。任务执行完成后,线程不会被销毁,而是放回线程池中以供下一次使用,这避免了频繁创建和销毁线程的开销。同时,线程池还可以限制线程的数量,避免线程数量过多导致资源竞争、上下文切换等问题,从而提高程序的执行效率。

1、线程是一种宝贵的资源,因此使用线程池可以减少创建和销毁线程的次数,从而提高应用程序的性能。线程池中的工作线程可以重复利用,减少了线程的创建和销毁开销。
2、通过调整线程池中工作线程的数量,可以根据系统的承受能力来适配线程池,防止过多的内存消耗导致服务器崩溃。这可以提高应用程序的可靠性和稳定性。

通过ThreadPoolExecutor创建自定义线程池

public class ThreadPool {public static final ThreadPoolExecutor threadPool;static{// 创建自定义线程池threadPool = new ThreadPoolExecutor(5,20,30,TimeUnit.SECONDS,new LinkedBlockingQueue<>(50),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());}public static void main(String[] args) {// 向线程池提交一个任务threadPool.execute(()->{int i = 10 / 1;System.out.println(Thread.currentThread().getName() + "running....");});}}

ThreadPoolExecutor创建线程池的7大参数

在这里插入图片描述

    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
参数名含义说明
corePoolSize核心线程数线程池初始化线程数为0;每提交一个任务就会新建一个线程,即使线程池中存在空闲的线程,但是只要线程数量没有达到该参数值,它也会创建一个新的线程来执行当前任务
maximumPoolSize线程池能创建的最大线程数线程池中线程数量能达到的最大值;当核心线程数已满,并且工作队列也已经存放满的情况下,才会去判断当前线程数是否小于maximumPoolSize,小于则继续创建线程处理任务maximumPoolSize
keepAliveTime闲置超时时间当线程池中的线程数corePoolSize的线程在经过keepAliveTime的时间依然没有任务执行,则销毁线程
unit超时时间单位参数keepAliveTime的时间单位
workQueue工作队列当核心线程们都在忙着处理任务,没有一个空闲的此时新提交的任务就会放到任务队列中,等待空闲的线程来执行
threadFactory线程池创建新线程的工厂常用来定义线程的名称,一般使用默认的Executors.defaultThreadFactory()即可
handler拒绝策略达到最大线程数的线程们没有空闲且工作队列已满,此时提交的新任务就可以使用该参数来进行相应的处理

线程池处理任务的执行流程

在这里插入图片描述

下面对线程做了区分:核心线程数和非核心线程数;但是它们本质都是现成根本就没有区别,只是叫法不同而已

线程池初始化线程数为0而不是corePoolSize;当任务被提交给线程池,首先判断核心线程数corePoolSize

  • 如果当前核心线程数量小于corePoolSize;就会重新创建新的核心线程来执行当前任务,即使在有空闲核心线程的情况下
  • 如果当前核心线程数量大于corePoolSize;就会考虑是否能暂时放入工作队列中。
    • 如果工作队列没满,就会将当前任务放入工作队列中等待空闲线程执行
    • 如果工作队列已满,就要考虑创建更多的线程(非核心线程),直至maximumPoolSize最大核心数
      • 核心线程正忙、队列已满的情况下才会一直持续创建非核心线程数直至最大线程数。所以当核心线程数 + 非核心线程数达到最大线程数的上限,那么这个任务只能执行拒绝策略的业务逻辑

假设:corePoolSize=5,maximumPoolSize=20,workQueue.capacity=50;当前100的并发任务,请简述线程池的处理流程
注意:这是高并发场景,根本不考虑出现空闲线程的情况

  1. 前5个任务会使线程池依次创建5个核心线程来执行任务
  2. 队列里还能塞50个任务,到这是55个任务
  3. 最大核心线程数是20,所以还会再依次创建15个线程来处理任务;到这是55 + 15 = 70个任务
  4. 剩下的30个任务只能执行拒绝策略;权衡利弊进行丢弃或同步run方法调用

四种RejectedExecutionHandler策略

在这里插入图片描述

AbortPolicy,丢弃并抛出异常

拒绝任务的处理程序,该处理程序将抛出RejectedExecutionException。这是ThreadPoolExecutorScheduledThreadPoolExecutor的默认处理程序。
在这里插入图片描述

DiscardPolicy,静默丢弃什么也不做

被拒绝任务的处理程序,它以静默方式丢弃被拒绝的任务。
在这里插入图片描述

DiscardOldestPolicy,丢弃最老后当前任务重新进池

被拒绝任务的处理程序,它丢弃最老(队列头部)的未处理请求,然后重试执行,如果线程池已被关闭,那么任务将被直接丢弃。
在这里插入图片描述

CallerRunsPolicy,同步调用run()执行任务

被拒绝任务的处理程序,它直接在execute()中调用被拒绝任务的run(),这样也可以执行任务,只不过是同步调用run()的方式;如果线程池已被关闭,那么任务将被直接丢弃。
在这里插入图片描述

Executors中四种常用的线程池

通常情况下通过ThreadPoolExecutor创建自定义线程池;也可以直接使用Executors工具中提供的一些已创建好的线程池。下面记录四种比较常用的线程池。
在这里插入图片描述

newCachedThreadPool

创建一个线程池,该线程池根据需要创建新线程,但在以前构造的线程可用时重用它们。这些池通常会提高执行许多短期异步任务程序的性能。执行调用将重用先前构造的线程(如果可用)。如果没有可用的现有线程,将创建一个新线程并将其添加到池中。60秒内未使用的线程将被终止并从缓存中删除。因此,该类型的池长时间保持空闲状态也不会消耗任何资源。请注意,可以使用ThreadPoolExecutor构造函数创建具有相似属性但不同细节(例如,超时参数)的池

    public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());// 最后缺失的两个参数将使用默认值                               }

特点:

  • 核心线程数为0,最大线程数为Integer最大值
  • 执行任务先复用线程,如果不能复用再创建新线程
  • 60s的空闲时间,因此不用担心没有任务的情况下资源浪费的问题
  • 通常会提高执行许多短期异步任务程序的性能

newFixedThreadPool

创建一个线程池,该线程池在无界队列上操作固定数量的线程,并在需要时使用提供的ThreadFactory创建新线程。核心线程数和最大线程数都是nThreads所以在任何时候,最多有nThreads线程处于活动状态处理任务。如果在所有线程都处于活动状态时提交额外的任务,它们将在队列中等待,直到其中一个线程可用。如果任何线程在关闭之前的执行过程中由于失败而终止,如果需要执行后续任务,则会有一个新线程取代它的位置(理解为:如果死了一个线程会立即创建一个新的线程来取代它的位置来执行后面的任务)。在显式关闭池之前,池中的线程将一直存在。

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory);}

特点

  • 固定的线程数量
  • 在池没有关闭之前会一直存活

newScheduledThreadPool

一个ThreadPoolExecutor,它可以额外安排命令在给定延迟后运行,或定期执行。当需要多个工作线程时,或者需要ThreadPoolExecutor(该类扩展)的额外灵活性或功能时,该类优于java.util.Timer。
延迟任务不会在启用后立即执行,但是在启用后,它们何时开始执行没有任何实时保证。为完全相同的执行时间安排的任务以先进先出(FIFO)的提交顺序启用。
当提交的任务在运行之前被取消时,执行将被抑制。默认情况下,这样一个被取消的任务不会自动从工作队列中删除,直到它的延迟结束。虽然这样可以进一步检查和监视,但也可能导致取消的任务无限制地保留。为了避免这种情况,使用setRemoveOnCancelPolicy使任务在取消时立即从工作队列中删除。
通过scheduleAtFixedRate或scheduleWithFixedDelay调度的周期性任务的连续执行不会重叠。虽然不同的线程可以执行不同的执行,但是之前执行的效果会在后续执行之前发生。
虽然这个类继承自ThreadPoolExecutor,但继承的一些调优方法对它没有用处。特别是,因为它使用corePoolSize线程和无界队列充当固定大小的池,所以对maximumPoolSize的调整没有任何有用的影响。此外,将corePoolSize设置为零或使用allowCoreThreadTimeOut几乎从来都不是一个好主意,因为这可能会使池中没有线程来处理任务,一旦它们有资格运行。
与ThreadPoolExecutor一样,如果没有特别指定,这个类使用Executors.defaultThreadFactory作为默认线程工厂,并且使用ThreadPoolExecutor。AbortPolicy作为默认的被拒绝执行处理程序。
扩展注意事项:该类覆盖execute和submit方法来生成内部schedulefuture对象,以控制每个任务的延迟和调度。为了保留功能,在子类中对这些方法的任何进一步重写都必须调用超类版本,这有效地禁用了额外的任务自定义。然而,这个类提供了另一种受保护的扩展方法decorateTask (Runnable和Callable各有一个版本),可用于自定义用于执行通过execute, submit, schedule, scheduleAtFixedRate和scheduleWithFixedDelay输入的命令的具体任务类型。默认情况下,ScheduledThreadPoolExecutor使用扩展FutureTask的任务类型。然而,这可以通过使用表单的子类来修改或替换:

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);}

newSingleThreadExecutor

创建一个在无界队列上操作单个工作线程的线程池。(但是请注意,如果这个单线程在关闭之前的执行过程中由于失败而终止(死亡了),如果需要执行后续任务,将会有一个新的线程取代它)。保证任务按顺序执行,并且在任何时间活动的线程数量不超过一个。与newFixedThreadPool(1)不同,返回的执行器保证不会被重新配置以使用其他线程(注释中这句话不理解,仅仅只是发现了它俩构造方法不同,如下图)。
在这里插入图片描述

特点:

  • 只有唯一的一个工作线程
  • 工作队列无边界,容易导致OOM
    public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}

上面源码可知核心线程数、最大线程数都是1,线程空闲回收时间配置也就没有意义了,所以给0,队列使用LinkedBlockingQueue这种无界的工作队列;剩余两个参数ThreadFactoryRejectedExecutionHandler都是用默认的
在这里插入图片描述

CompletableFuture异步编排

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

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

相关文章

CSS3网页布局基础

CSS布局始于第2个版本&#xff0c;CSS 2.1把布局分为3种模型&#xff1a;常规流、浮动、绝对定位。CSS 3推出更多布局方案&#xff1a;多列布局、弹性盒、模板层、网格定位、网格层、浮动盒等。本章重点介绍CSS 2.1标准的3种布局模型&#xff0c;它们获得所有浏览器的全面、一致…

信息泄露--

大唐电信AC简介 大唐电信科技股份有限公司是电信科学技术研究院&#xff08;大唐电信科技产业集团&#xff09;控股的的高科技企业&#xff0c;大唐电信已形成集成电路设计、软件与应用、终端设计、移动互联网四大产业板块。 大唐电信AC集中管理平台存在弱口令及敏感信息泄漏漏…

【使用Python编写游戏辅助工具】第一篇:概述

引言 欢迎阅读本系列文章&#xff0c;本系列将带领读者朋友们使用Python来实现一个简单而有趣的游戏辅助工具。 写这个系列的缘由源自笔者玩了一款游戏。正巧&#xff0c;笔者对Python编程算是有一定的熟悉&#xff0c;且Python语言具备实现各种有趣功能的能力&#xff0c;因…

Vue 事件绑定 和 修饰符

目录 一、事件绑定 1.简介 : 2.实例 : 二、修饰符 1.简介 : 2.实例 : 3.扩展 : 一、事件绑定 1.简介 : (1) 在Vue中&#xff0c;通过"v-on:事件名"可以绑定事件&#xff0c;eg : v-on:click表示绑定点击事件。 (2) 触发事件时调用的方法&#xff0c;定义在Vu…

右击文件或者文件夹使用vscode打开

平常我们在打开项目时&#xff0c;经常会需要快捷打开方式&#xff0c;直接使右键使用编辑器打开&#xff0c;但是有时在安装时忘记了选择 “Add “Open with Code” action to Windows Explorer file context menu” 在Windows资源管理器文件上下文菜单中添加“用代码打开”操…

1360. 日期之间隔几天

1360. 日期之间隔几天 Java代码&#xff1a; 【DateFormat】DateFormat用于实现日期的格式化 import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; // 好像已过时class Solution {public int daysBet…

docker打包container成image,然后将image上传到docker hub

第一步&#xff1a;停止正在运行的容器 docker stop <container_name> eg: docker stop xuanjie_mlir 第二步&#xff1a;将对应的container打包成image docker commit <container_id> <镜像名&#xff1a;版本> eg&#xff1a;docker commit 005672e6d97a…

JVM——类的生命周期(加载阶段,连接阶段,初始化阶段)

目录 1.加载阶段2.连接阶段1.验证2.准备3.解析 3.初始化阶段4.总结 类的生命周期 1.加载阶段 ⚫ 1、加载(Loading)阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息。 程序员可以使用Java代码拓展的不同的渠道。 ⚫ 2、类加载器在加载完类…

Tailwind CSS vs 现代CSS,Tailwind CSS 会像CSS-in-JS 一样亡?

本文是 关于Tailwind CSS 与 现代 CSS之间比较的文章。文章中作者详细比较了这两种CSS开发方法的优缺点。他指出&#xff0c;Tailwind CSS是一种基于类的CSS框架&#xff0c;提供了快速开发网站的便利性&#xff0c;但可能导致HTML代码的臃肿。另一方面&#xff0c;现代CSS方法…

2023/11/2 JAVA学习

接口里面只有这两个东西,无构造器,代码块之类的 私有方法可以在接口里的其他默认方法,或私有方法中访问 静态方法,类持有,可直接调用 接口多继承,可以一个接口继承其他几个接口把几个接口合并成一个接口 先创建外部类,再创建成员内部类 在外部类中无法直接访问内部类的方法变量…

Git 指令白雪警告!在IDEA中配置使用Git管理提交代码,无需繁杂指令

目录 1. 前言 2. Git 路径配置步骤 3. IDEA中使用Git管理项目 3.1 第一种做法 3.2 第二种做法 4. IDEA中提交代码和推送代码 5. 分支相关操作 5.1 创建分支 5.2 切换分支&#xff0c;删除分支 6. 拉取更新代码并处理分支冲突 1. 前言 相信有很多小伙伴在学习 Git 指…

EasyExcel动态复杂表头导出方法

目录 需求分析解决方案数据问题数据导入 需求分析 公司数据比较特殊有一部分数据需要动态修改导致信息导入时表头是不确定的&#xff0c;但其中又有一部分表头是固定的&#xff0c;如下图所示&#xff0c;如果表头全部是固定的话可以通过EasyExcel实体类的注解很轻松的解决&am…