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

news2024/11/15 5:55:28

文章目录

  • 创建线程的四种方式
    • 方式一、继承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 {
        @Override
        public 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.coloradmin.cn/o/1164248.html

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

相关文章

在校园跑腿系统小程序中,如何设计高效的实时通知与消息推送系统?

1. 选择合适的消息推送服务 在校园跑腿系统小程序中&#xff0c;选择一个适合的消息推送服务。例如&#xff0c;使用WebSocket技术、Firebase Cloud Messaging (FCM)、或第三方推送服务如Pusher或OneSignal等。注册并获取相关的API密钥或访问令牌。 2. 集成服务到小程序后端…

(1)上位机底部栏 UI如何设置

上位机如果像设置个多页面切换&#xff1a; 位置&#xff1a; 代码如下&#xff1a; "tabBar": {"color": "black","selectedColor": "#d43c33","borderStyle":"black","backgroundColor": …

数据库 | 看这一篇就够了!最全MySQL数据库知识框架!

大家好&#xff01; 作为一名程序员&#xff0c;每天和各种各样的“数据库”打交道&#xff0c;已经成为我们的日常。当然&#xff0c;立志成为一名超级架构师的我&#xff0c;肯定要精通这项技能。咳咳&#xff01;不过饭还是要一口一口吃的&#xff0c;“数据库”这个内容实在…

黄执中老师人际说服课思考总结(个人笔记整理 ①)

问题描述和解决方法&#xff1a; &#x1f624;职场中明明是ta应该做的事&#xff0c;ta为何还生气呢&#xff1f;&#xff1b; &#x1f620;不知道怎么和家人孩子沟通&#xff1f;自己明明是对的&#xff0c;可别人就是不听 &#x1f621;不知道怎么安慰朋友&#xff1f;&…

Python time strptime()和strftime()

1 strptime()方法 根据指定的格式把一个时间字符串解析为时间元组 重要的时间日期格式化符号 %y 两位数的年份表示&#xff08;00-99&#xff09; %Y 四位数的年份表示&#xff08;000-9999&#xff09; %m 月份&#xff08;01-12&#xff09; %d 月内中的一天&#xff08;0-…

主机ping、ssh连接不通本地虚拟机

一、问题描述 在使用vscode remote ssh时&#xff0c;连接timeout&#xff0c;而且主机无论如何也ping不通虚拟机&#xff0c;但是虚拟机可以ping通主机。通过vagrant也可以连接虚拟机。 二、解决方案 试了网上包括设置remote ssh在内的许多方法都不行。重新查看主机和虚拟机…

C++类和对象-->默认成员函数

文章目录 类的6个默认成员函数初始化和清理构造函数构造函数概念构造函数特征 析构函数析构函数概念析构函数特征 拷贝赋值拷贝构造函数拷贝构造函数概念拷贝构造函数特征 赋值运算重载运算符重载运算符重载特征 赋值运算符重载赋值运算符特征 取地址重载取地址操作符重载const…

C#中使用LINQtoSQL管理SQL数据库之添加、修改和删除

目录 一、添加数据 二、修改数据 三、删除数据 四、添加、修改和删除的源码 五、生成效果 1.VS和SSMS原始记录 2.删除ID2和5的记录 3.添加记录ID2、5和8 4.修改ID3和ID4的记录 用LINQtoSQL管理SQL Server数据库时&#xff0c;主要有添加、修改和删除3种操作。 项目中创…

Unity AssetBundle批量打包、加载(场景、Prefab)完整流程

目录 1、文章介绍 2、具体思路和写法 &#xff08;1&#xff09;AB包的打包 &#xff08;2&#xff09;AB包的加载 &#xff08;3&#xff09;AB包卸载 3、结语 1、文章介绍 本篇博客主要起记录和学习作用&#xff0c;简单的介绍一下AB包批量的打包和加载AB包的方式&…

项目实战:编辑页面加载库存信息

1、前端编辑页面加载水果库存信息逻辑edit.js let queryString window.location.search.substring(1) if(queryString){var fid queryString.split("")[1]window.onloadfunction(){loadFruit(fid)}loadFruit function(fid){axios({method:get,url:edit,params:{fi…

【IIS搭建网站】在本地电脑上搭建web服务器并实现外网访问

文章目录 1.前言2.Windows网页设置2.1 Windows IIS功能设置2.2 IIS网页访问测试 3. Cpolar内网穿透3.1 下载安装Cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5.结语 1.前言 在网上各种教程和介绍中&#xff0c;搭建网页都会借助各种软件的帮助&#xf…

【APP】go-musicfox - 一款网易云音乐命令行客户端, 文件很小Mac版本只有16.5M

go-musicfox 是用 Go 写的又一款网易云音乐命令行客户端&#xff0c;支持各种音质级别、UnblockNeteaseMusic、Last.fm、MPRIS 和 macOS 交互响应&#xff08;睡眠暂停、蓝牙耳机连接断开响应和菜单栏控制等&#xff09;等功能特性。 预览 启动 启动界面 主界面 主界面 通…

【docker】安装 showdoc

1. 下载镜像 2.新建存放showdoc数据的目录 3.启动showdoc容器 4.打开网页 1. 下载镜像 # 原版官方镜像安装命令(中国大陆用户不建议直接使用原版镜像&#xff0c;可以用后面的加速镜像) docker pull star7th/showdoc # 中国大陆镜像安装命令&#xff08;安装后记得执行docke…

数组反转(LeetCode)

凑数 ... 描述 : 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] &#xff0c;就返回 0。 假设环境不允许存储 64 位整数&#xff08;有符号或无符号&#xff09;。 题目…

Dubbo中的负载均衡算法之一致性哈希算法

Dubbo中的负载均衡算法之一致性哈希算法 哈希算法 假设这样一个场景&#xff0c;我们申请一台服务器来缓存100万的数据&#xff0c;这个时候是单机环境&#xff0c;所有的请求都命中到这台服务器。后来业务量上涨&#xff0c;我们的数据也从100万上升到了300万&#xff0c;原…

VS使用小技巧——如何让别人看不到你写的代码,却能够运行你的代码

VS使用小技巧 前言方法使用静态库的示例如何创建静态库如何导入静态库Xcode里导入静态库VS2022导入静态库 前言 在实际生活中&#xff0c;作为程序员偶尔会因为资金不够用了选择去兼职写代码&#xff0c;当我们写完一个代码&#xff0c;将他发给某个公司的时候&#xff0c;我们…

C++——定义一个 Box(盒子)类,在该类定义中包括数据成员和成员函数

完整代码&#xff1a; /*定义一个 Box(盒子)类&#xff0c;在该类定义中包括数据成员和成员函数。 数据成员&#xff1a;length &#xff08;长&#xff09;、width&#xff08;宽&#xff09;和 height&#xff08;高&#xff09;&#xff1b; 成员函数&#xff1a;构造函数 …

阿里云双十一大促:云服务器1年99元,新老同享,续费同价!

2023年双十一购物狂欢节来临&#xff0c;阿里云推出了金秋云创季活动&#xff0c;活动力度很大&#xff0c;不再是老用户与狗不得入内&#xff0c;2核2G3M云服务器1年99元&#xff0c;新老同享&#xff0c;续费同价&#xff01; 活动地址&#xff1a;传送门>>> 活动详…

【C++】C++11【下】lambda表达式|thread线程库

目录 1、lambda表达式 1.1 lambda表达式的引入 1.2 lambda表达式的语法 1.3 lambda表达式的原理 2、线程库 2.1thread类的介绍 2.2 线程函数参数 2.3 原子性操作库(atomic) 2.4 使用场景 应用场景1&#xff1a; 应用场景2: 应用场景3&#xff1a; 应用场景4&#xf…

SAFe大规模敏捷框架

Leangoo领歌是一款永久免费的专业的敏捷开发管理工具&#xff0c;提供端到端敏捷研发管理解决方案&#xff0c;涵盖敏捷需求管理、任务协同、进展跟踪、统计度量等。 
 Leangoo领歌上手快、实施成本低&#xff0c;可帮助企业快速落地敏捷&#xff0c;提质增效、缩短周期、加速…