聊聊并发编程——线程池

news2024/11/25 0:42:17

目录

Java线程池

处理流程

线程池主要参数

常见的拒绝策略

execute和submit区别

关闭线程池

常见的线程池

newSingleThreadExecutor

newFixedThreadPool

newCachedThreadPool

newScheduledThreadPool

线程池的状态


Java线程池

运用场景最多的并发框架,几乎所有需要异步或者并发执行任务的程序都可以使用线程池。如下好处:

  • 降低资源消耗。重复利用已创建的线程降低线程创建和销毁造成的消耗。

  • 提高响应速度。当任务到达时,不需要等到线程创建就能立即执行。

  • 提高线程的客观理性。使用线程池可以进行统一分配、调优和监控。

处理流程

当新任务到达线程池的时候,处理流程如下:

  1. 判断核心线程池里的线程是否都在执行任务:

    • 否,创建新的工作线程执行任务。

    • 是,去看阻塞队列。

  2. 判断阻塞队列是否满了:

    • 否,将新任务存储在阻塞队列中。

    • 是,去看整个线程池。

  3. 判断线程池的线程是否都处于工作状态:

    • 否,创建新的工作线程执行任务。

    • 是,去看拒绝策略吧。

  4. 根据拒绝策略进行处理。

ThreadPoolExecutor执行execute方法分为下面4中情况:

  1. 当前运行的线程少于corePoolSize,创建新线程执行任务。(需获取全局锁)

  2. 运行的线程等于或多余corePoolSize,则将任务加入BlockingQueue。

  3. 如果队列已满,且运行线程数少于maximumPoolSize,创建新线程执行任务。(需获取全局锁)

  4. 如果创建新线程将使得当前运行的线程超过maximumPoolSize,任务被拒绝,执行RejectedExecutionHandler.rejectedExecution()方法。

网上有看到一个生动的例子,可能会让大家印象更深刻些:

银行的营业大厅有6个业务窗口,现在开放了3个,有3个业务员负责办理业务。这天,二柱去办理业务,他可能会遇到什么情况呢?

  1. 3个业务窗口正好空闲,他可以直接去办理业务。

  2. 3个业务窗口都有人,所以他得去排队。

  3. 3个业务窗口都有人而且队也排满了。这是经理赶紧开放了另外3个窗口,排队的可以先去办理。

  4. 6个业务窗口都有人了,队伍也排满了。二柱子去找经理,经理说明天再来或者先不办了。

线程池主要参数
  1. 核心线程数(corePoolSize): 核心线程数是线程池中保持活动状态的最小线程数量。即使线程池中没有任务需要执行,核心线程也会保持活动状态,不会被销毁。核心线程数通常用来处理短期生存期的任务,以减少线程的创建和销毁开销。

  2. 最大线程数(maximumPoolSize): 最大线程数是线程池中允许存在的最大线程数量。当任务数量超过核心线程数并且任务队列已满时,线程池会创建新的线程,但不会超过最大线程数。最大线程数可以控制线程池的最大并发性。

  3. 任务队列(BlockingQueue): 任务队列用于存储等待执行的任务。当线程池中的线程数达到核心线程数时,新的任务会被放入任务队列中等待执行。任务队列可以是不同类型的队列,如无界队列(如 LinkedBlockingQueue)或有界队列(如 ArrayBlockingQueue)。

  4. 线程存活时间(keepAliveTime): 线程存活时间是在核心线程数之外的线程在没有任务可执行时保持活动状态的最长时间。当线程池中的线程数量超过核心线程数,空闲的非核心线程在经过一定时间后会被销毁,以减少资源占用。

  5. 拒绝策略(RejectedExecutionHandler): 拒绝策略定义了当线程池无法接受新的任务时应该采取的动作。常见的拒绝策略包括抛出异常、丢弃任务、丢弃最旧的任务、调用提交任务的线程来执行任务等。

  6. 线程工厂(ThreadFactory): 线程工厂用于创建线程池中的线程。通过自定义线程工厂,可以为线程池中的线程设置自定义的名称、优先级、守护状态等属性。

常见的拒绝策略
  1. AbortPolicy(默认策略): 这是默认的拒绝策略,它会抛出一个 RejectedExecutionException 异常,告诉调用者线程池已满,无法处理新任务。这是最常用的拒绝策略,它会防止任务堆积,但可能会导致任务丢失。

    ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize, 
        maximumPoolSize, 
        keepAliveTime, 
        TimeUnit.SECONDS, 
        workQueue,
        new ThreadPoolExecutor.AbortPolicy());
  2. CallerRunsPolicy: 这个策略不会抛出异常,而是将任务回退到调用者,让调用者来执行。这可以用于保证任务的执行,但可能会导致调用者的线程阻塞。

    ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize, 
        maximumPoolSize, 
        keepAliveTime, 
        TimeUnit.SECONDS, 
        workQueue,
        new ThreadPoolExecutor.CallerRunsPolicy());
  3. DiscardPolicy: 这个策略会默默地丢弃掉无法处理的新任务,不会抛出异常。这可能会导致任务丢失,但对于不太重要的任务可以使用。

    ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize, 
        maximumPoolSize, 
        keepAliveTime, 
        TimeUnit.SECONDS, 
        workQueue,
        new ThreadPoolExecutor.DiscardPolicy());
  4. DiscardOldestPolicy: 这个策略会丢弃队列中最旧的任务,然后尝试重新提交新任务。它可能会导致一些任务被丢弃,但对于有优先级的任务可能是一个不错的选择。

    ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize, 
        maximumPoolSize, 
        keepAliveTime, 
        TimeUnit.SECONDS, 
        workQueue,
        new ThreadPoolExecutor.DiscardOldestPolicy());
  5. 自定义策略: 你也可以自定义拒绝策略,只需实现 RejectedExecutionHandler 接口的 rejectedExecution 方法,然后将其传递给线程池的构造函数。

    class CustomRejectPolicy implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            // 自定义拒绝策略的逻辑
        }
    }
    ​
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize, 
        maximumPoolSize, 
        keepAliveTime, 
        TimeUnit.SECONDS, 
        workQueue,
        new CustomRejectPolicy());

选择适当的拒绝策略取决于应用程序的需求。通常,如果任务的重要性很高,你可能会选择将任务回退到调用者或者使用自定义策略来处理。如果任务不太重要,你可以使用默认的 AbortPolicyDiscardPolicy 策略。需要根据具体情况权衡任务的执行和资源利用之间的权衡。

execute和submit区别

可以使用两个方法向线程池提交任务,分别为execute()和submit()方法。

execute()方法用于提交不需要返回值的任务。

 threadsPool.execute(new Runnable() { 
     @Override 
     public void run() { 
         // TODO Auto-generated method stub 
     } 
 });

submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个 future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方 法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线 程一段时间后立即返回,这时候有可能任务没有执行完。

Future future = executor.submit(harReturnValuetask); 
    try { 
        Object s = future.get(); 
    } catch (InterruptedException e) { 
        // 处理中断异常 
    } catch (ExecutionException e) { 
        // 处理无法执行任务异常 
    } finally { 
        // 关闭线程池 
        executor.shutdown(); 
    }
关闭线程池

可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。

shutdown() 将线程池状态置为shutdown,并不会⽴即停⽌:

  1. 停⽌接收外部submit的任务

  2. 内部正在跑的任务和队列⾥等待的任务,会执⾏完

  3. 等到第⼆步完成后,才真正停⽌

shutdownNow() 将线程池状态置为stop。⼀般会⽴即停⽌,事实上不⼀定:

  1. 和shutdown()⼀样,先停⽌接收外部提交的任务

  2. 忽略队列⾥等待的任务

  3. 尝试将正在跑的任务interrupt中断

  4. 返回未执⾏的任务列表

shutdown 和shutdownnow简单来说区别如下:

  • shutdownNow()能⽴即停⽌线程池,正在跑的和正在等待的任务都停下了。这样做⽴即⽣效,但是⻛险也⽐ 较⼤。

  • shutdown()只是关闭了提交通道,⽤submit()是⽆效的;⽽内部的任务该怎么跑还是怎么跑,跑完再彻底停 ⽌线程池。

常见的线程池

常见的有四种,都是通过Executors 工具类创建的,但是不建议。Executors各个方法的弊端:newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

newSingleThreadExecutor

直接调用ThreadPoolExecutor的构造⽅法。

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory));
}

线程池特点:

  • 核心线程数1

  • 最大线程数1

  • 阻塞队列是无界队列LinkedBlockingQueue,可能会导致OOM

  • keepAliveTime为0

使用场景:串行执行任务的场景,一个任务一个任务地执行

newFixedThreadPool

直接调用ThreadPoolExecutor的构造⽅法。

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

线程池特点:

  • 核心线程数和最大线程数大小一样

  • keepAliveTime为0

  • 阻塞队列是无界队列LinkedBlockingQueue,可能会导致OOM

使用场景:适用于执行长期的任务

newCachedThreadPool

直接调用ThreadPoolExecutor的构造⽅法。

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory);
}

线程池特点:

  • 核心线程数0

  • 最大线程数Integer.MAX_VALUE,可能无限创建线程,导致OOM

  • 阻塞队列是SynchronousQueue

  • 非核心线程空闲存活时间为60s

使用场景:并发执行大量短期的小任务

newScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}

线程池特点:

  • 最大线程数Integer.MAX_VALUE,可能无限创建线程,导致OOM

  • 阻塞队列DelayedWorkQueue

  • keepAliveTime为0

  • scheduleAtFixedRate() :按某种速率周期执⾏

  • scheduleWithFixedDelay():在某个延迟后执⾏

使用场景:周期性执行任务的场景,需要限制线程数量的场景

线程池的状态
  1. RUNNING(运行中): 线程池处于运行状态,可以接受新的任务,并且正在执行任务队列中的任务。

  2. SHUTDOWN(关闭中): 线程池进入关闭状态,不再接受新的任务提交,但会继续执行已经提交的任务,直到所有任务都完成。

  3. STOP(停止中): 线程池进入停止状态,不再接受新的任务提交,并且尝试中断正在执行的任务。这个状态会尽量终止线程池的执行。

  4. TIDYING(整理中): 线程池正在执行清理操作。当线程池的任务都完成后,它会进入这个状态,执行一些必要的清理操作,例如关闭线程。

  5. TERMINATED(已终止): 线程池已经完全终止,不再有任何活动线程。线程池的状态将永远停留在这个状态。

线程池的状态通常通过调用线程池的方法来进行转换,例如 shutdown() 方法将线程池从 RUNNING 状态转换为 SHUTDOWN 状态,shutdownNow() 方法将线程池从 RUNNING 状态转换为 STOP 状态。一旦线程池进入 TERMINATED 状态,就无法再切换到其他状态。

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

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

相关文章

【面试总结大纲】

面试 springSpring AOP的具体实现核心概念分别指的是什么?基于注解的切面实现主要包括以下几个步骤&#xff1a;两个切面&#xff0c;它们之间的顺序是怎么控制的 springmvc的工作流程设计模式原则Spring 框架中用到了哪些设计模式&#xff1f; spring Spring AOP的具体实现 …

讲讲项目里的仪表盘编辑器(三)布局组件

布局容器处理 看完前面两章的讲解&#xff0c;我们对仪表盘系统有了一个大概的理解。接着我们讲讲更深入的应用。 上文讲解的编辑器只是局限于平铺的组件集。而在编辑器中&#xff0c;还会有一种组件是布局容器。它允许其他组件拖拽进入在里面形成自己的一套布局。典型的有分页…

如何实现电脑语音输入功能?

现在的手机都具备语音输入功能&#xff0c;并且识别率非常高&#xff0c;语音输入是目前最快速的文字输入方式&#xff0c;但是电脑上却无语音输入的功能&#xff0c;那么如何实现在电脑端也可进行语音输入的梦想呢&#xff1f;现在介绍一款小工具“书剑电脑语音输入法”&#…

Llama2-Chinese项目:4-量化模型

一.量化模型调用方式   下面是一个调用FlagAlpha/Llama2-Chinese-13b-Chat[1]的4bit压缩版本FlagAlpha/Llama2-Chinese-13b-Chat-4bit[2]的例子&#xff1a; from transformers import AutoTokenizer from auto_gptq import AutoGPTQForCausalLM model AutoGPTQForCausalLM…

DDD项目落地之充血模型实践

一、背景 充血模型是DDD分层架构中实体设计的一种方案&#xff0c;可以使关注点聚焦于业务实现&#xff0c;可有效提升开发效率、提升可维护性&#xff1b; 二、DDD项目落地整体调用关系 调用关系图中的Entity为实体&#xff0c;从进入领域服务&#xff08;Domin&#xff09;…

基于Java的健身房会员管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

【算法挨揍日记】day10——704. 二分查找、34. 在排序数组中查找元素的第一个和最后一个位置

704. 二分查找 704. 二分查找 题目描述&#xff1a; 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 解题思路&…

使用序列到序列深度学习方法自动睡眠阶段评分

深度学习方法&#xff0c;用于使用单通道脑电图进行自动睡眠阶段评分。 def build_firstPart_model(input_var,keep_prob_0.5):# List to store the output of each CNNsoutput_conns []######### CNNs with small filter size at the first layer ########## Convolutionnetw…

Ant-Design-Vue:a-range-picker组件国际化配置

在使用Ant-Design-Vue中的时间范围选择器开发个人项目时&#xff0c;发现默认显示为英文。如何解决呢&#xff1f; date-picker分类 Antd-Vue提供了DatePicker、MonthPicker、RangePicker、WeekPicker 几种类型的时间选择器&#xff0c;分别用于选择日期、月份、日期范围、周范…

JSON的MIME媒体类型是application/json

JSON&#xff08;全称 JavaScript Object Notation&#xff09;即JavaScript对象表示法&#xff0c;通知使用application/json媒体类型。 目录 1、JSON介绍 2、JSON语法 3、实践总结 运行环境&#xff1a; Windows-7-Ultimate-x64、Windows-10-BusinessEditions-21h2-x64 1…

最新AI智能问答系统源码/AI绘画系统源码/支持GPT联网提问/Prompt应用+支持国内AI提问模型

一、AI创作系统 SparkAi创作系统是基于国外很火的ChatGPT进行开发的AI智能问答系统和AI绘画系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图…

总部位于德国的拉丁美洲在线杂货配送服务商Jokr完成5000万美元D轮融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于德国柏林的拉丁美洲在线杂货配送服务提供商Jokr今日宣布已完成5000万美元D轮融资。 本轮融资完后Jokr的估值已达到8亿美金&#xff0c;本轮融资由convialit Ventures领投&#xff0c;Lomba…

XDM,10.1

XDM&#xff0c;今天是国庆&#xff0c;就没有其他啥事情&#xff0c;祝大家国庆节快乐&#xff0c;玩的开心。 这两天放假也有时间捣鼓自己的事情了&#xff0c;挺开心的&#xff0c;第一件事就是把自己的一个小开发板修好了&#xff0c;然后自己的小os也能跑了几个假的线程。…

基于Javaweb的护肤品推荐系统 /基于ssm的护肤品销售系统

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&a…

RabbitMQ之发布确认高级

RabbitMQ之发布确认高级 一、发布确认 SpringBoot 版本1.1 确认机制方案1.2 代码架构图1.3 配置文件1.4 添加配置类1.5 消息生产者1.6 回调接口1.7 消息消费者1.8 结果分析 二、回退消息2.1 Mandatory 参数2.2 消息生产者代码2.3 回调接口2.4 结果分析 三、备份交换机3.1 代码架…

在 Windows 终端运行已有的 Python 程序

在同一个路径下&#xff0c;输入全名&#xff0c;如图&#xff1a;

xss盲打

1.场景 2.类别 3.危害 4.构造方法 5.验证 6.利用 7.攻击方法 8.防御手段 9.案例此处有构造网站 10.js代码 11.类型

基于Java的旅游线路线推荐系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

常见C++缩写知识点

1.ADLc - What is "Argument-Dependent Lookup" (aka ADL, or "Koenig Lookup")? - Stack Overflow 有空写写cout。。 2.using继承 C11 中的继承构造函数 | 菜鸟教程

linux系统与应用

Windows中的硬盘和盘符的关系&#xff1b; 硬盘通常为一块到两块&#xff1b;数量与盘符没有直接关系&#xff1b;一块硬盘可以分为多个盘符&#xff0c;如c,d,e,f,g等&#xff1b;当然理论上也可以一块硬盘只有一个盘符&#xff1b;学习linux时&#xff0c;最好使用固态硬盘&a…