【JAVA】:万字长篇带你了解JAVA并发编程【二】

news2025/4/22 10:40:15

目录

  • 【JAVA】:万字长篇带你了解JAVA并发编程【二】
    • 3. 线程池
      • 池化技术
      • 线程池的概念与作用
        • 什么是线程池?
        • 优点
      • Executor框架
        • Executor框架组成部分
          • 工作任务
          • 抽象接口和类
      • 线程池的生命周期
      • 非核心线程和核心线程
      • ThreadPoolExecutor
      • 线程池的工作流
      • 常见的线程池
        • Executors 的 4 个功能线程有如下`弊端`:
        • 合理设置线程池参数
    • 结语

个人主页: 【⭐️个人主页】
需要您的【💖 点赞+关注】支持 💯


【JAVA】:万字长篇带你了解JAVA并发编程【二】

3. 线程池

池化技术

线程的创建、销毁都会带来一定的开销

如果当我们需要使用到多线程时再去创建,使用完又去销毁,这样去使用不仅会拉长业务流程,还会增加创建、销毁线程的开销

于是有了池化技术的思想,将线程提前创建出来,放在一个池子(容器)中进行管理

当需要使用时,从池子里拿取一个线程来执行任务,执行完毕后再放回池子

不仅是线程有池化的思想,连接也有池化的思想,也就是连接池

池化技术不仅能复用资源提高响应还方便管理

线程池的概念与作用

什么是线程池?

线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。在JAVA中主要是使用ThreadPoolExecutor类来创建线程池,并且JDK中也提供了Executors工厂类来创建线程池(不推荐使用)。

❗❗❗ 这就是线程池最核心的设计思路,「复用线程,平摊线程的创建与销毁的开销代价」。

优点

相比于来一个任务创建一个线程的方式,使用线程池的优势体现在如下几点:

  • 降低资源消耗,复用已创建的线程来降低创建和销毁线程的消耗。
  • 提高响应速度,任务到达时,可以不需要等待线程的创建立即执行。
  • 提高线程的可管理性,使用线程池能够统一的分配、调优和监控。

我们先看一张线程池相关接口的类图结构,网上盗来的,但画的还是很全面的
在这里插入图片描述
右上角的几个接口可以先不看,等我们介绍到组合任务的时候会继续说的,我们看左边,Executor、ExecutorService 以及 AbstractExecutorService 都是我们熟悉的,它们抽象了任务执行者的基本模型。

ThreadPoolExecutor 是对线程池概念的抽象,它天生实现了任务执行的相关接口,也就是说,线程池也是一个任务的执行者,允许你向其中提交多个任务,线程池将负责分配线程与调度任务。

至于 Schedule 线程池,它是扩展了基础的线程池实现,提供「计划调度」能力,定时调度任务,延时执行等。

Executor框架

Executor是一套线程池管理框架。是JDK 1.5中引入的一系列并发库中与Executor相关的功能类,其中最核心的类就是常见的ThreadPoolExecutor
Executor将工作任务线程池进行分离解耦

Executor框架组成部分
工作任务

工作任务被分为两种:无返回结果的Runnable有返回结果的Callable

在线程池中允许执行这两种任务,其中它们都是函数式接口,可以使用lambda表达式来实现

Future接口用来定义获取异步任务的结果,它的实现类常是FutureTask

线程池在执行Callable任务时,会将使用FutureTask将其封装成Runnable执行,因此Executor的执行方法入参只有Runnable

FutureTask相当于适配器,将Callable转换为Runnable再进行执行
例子:

  @Test
    public void testFutureTask() throws ExecutionException, InterruptedException {
        FutureTask<String> command = new FutureTask<String>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(5000);
                return "hello world";
            }
        });
        result.execute(command);
        System.out.println("获取异步结果:start");
        String s = command.get();
        System.out.println("获取异步结果:" + s);

    }
抽象接口和类

Executor接口:只有一个execute()方法;
ExecutorService接口: ExecutorService扩展了Executor接口,增加了生命周期的管理方法。
ExecutorService的生命周期包括三种状态:运行关闭终止

创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了submit()的任务,当已经提交了的任务执行完后,便到达终止状态。

ScheduledExecutorService接口:任务调度的线程池实现,可以在给定的延迟后运行命令或者定期执行命令;

ThreadPoolExecutor:最核心的线程池实现,用来执行被提交的任务;

线程池的生命周期

总共有五种状态:
在这里插入图片描述

  • RUNNING(111) :能接受新提交的任务,并且也能处理阻塞队列中的任务;

  • SHUTDOWN(000):关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);

  • STOP(001):不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow()方法会使线程池进入到该状态;

  • TIDYING(010):如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。

  • TERMINATED(011):在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。

ThreadPoolExecutor 使用int的高三位表示线程池状态,低29位表示线程数量;

非核心线程和核心线程

什么是“非核心线程”?

核心线程跟创建的先后没有关系,而是跟工作线程的个数有关,如果当前工作线程的个数大于核心线程数,那么所有的线程都可能是“非核心线程”,都有被回收的可能。
一个线程执行完了一个任务后,会去阻塞队列里面取新的任务,在取到任务之前它就是一个闲置的线程。

线程回收策略

取任务的方法有两种,一种是通过 take() 方法一直阻塞直到取出任务,另一种是通过 poll(keepAliveTime,timeUnit) 方法在一定时间内取出任务或者超时,如果超时这个线程就会被回收,请注意核心线程一般不会被回收。

那么怎么保证核心线程不会被回收呢?

还是跟工作线程的个数有关,每一个线程在取任务的时候,线程池会比较当前的工作线程个数与核心线程数:

如果工作线程数小于当前的核心线程数,则使用第一种方法取任务,也就是没有超时回收,这时所有的工作线程都是“核心线程”,他们不会被回收;

如果大于核心线程数,则使用第二种方法取任务,一旦超时就回收,所以并没有绝对的核心线程,只要这个线程没有在存活时间内取到任务去执行就会被回收。

核心线程一般不会被回收,但是也不是绝对的,如果我们设置了允许核心线程超时被回收的话,那么就没有核心线程这种说法了,所有的线程都会通过 poll(keepAliveTime, timeUnit) 来获取任务,一旦超时获取不到任务,就会被回收,一般很少会这样来使用,除非该线程池需要处理的任务非常少,并且频率也不高,不需要将核心线程一直维持着。

非核心线程存活时间

当工作线程数达到 corePoolSize 时,线程池会将新接收到的任务存放在阻塞队列中,而阻塞队列又两种情况:一种是有界的队列,一种是无界的队列。

如果是无界队列,那么当核心线程都在忙的时候,所有新提交的任务都会被存放在该无界队列中,这时最大线程数将变得没有意义,因为阻塞队列不会存在被装满的情况。

如果是有界队列,那么当阻塞队列中装满了等待执行的任务,这时再有新任务提交时,线程池就需要创建新的“临时”线程来处理,相当于增派人手来处理任务。

但是创建的“临时”线程是有存活时间的,不可能让他们一直都存活着,当阻塞队列中的任务被执行完毕,并且又没有那么多新任务被提交时,“临时”线程就需要被回收销毁,在被回收销毁之前等待的这段时间,就是非核心线程的存活时间,也就是 keepAliveTime 属性。

ThreadPoolExecutor

ThreadPoolExecutor 的创建并不复杂,直接 new 就好,只不过构造函数有好久个重载,我们直接看最底层的那个,也就是参数最多的那个。

public ThreadPoolExecutor
(   int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler
)

参数介绍
创建线程池需要以下几个参数,其中有5个是必需的:

  • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
  • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
    handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
  • RejectedExecutionHandler 拒绝策略:当线程不够用,并且阻塞队列爆满时如何拒绝任务的策略
拒绝策略作用
AbortPolicy 默认抛出异常
CallerRunsPolicy调用线程来执行任务
DiscardPolicy不处理,丢弃
DiscardOldestPolicy丢弃队列中最近一个任务,并立即执行当前任务

线程池的工作流

在这里插入图片描述

任务数 <= 核心线程数时,线程池中工作线程 = 任务数

核心线程数 + 队列容量 < 任务数 <= 最大线程数 + 队列容量时,工作线程数 = 任务数 - 队列容量

常见的线程池

Executors已经为我们封装好了 4 种常见的功能线程池,如下:

  • 定长线程池(FixedThreadPool)

    只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。适用于控制线程最大并发数。

  • 定时线程池(ScheduledThreadPool )

    核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。适用于执行定时或周期性的任务。

  • 可缓存线程池(CachedThreadPool)

    无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。适用于执行大量、耗时少的任务。

  • 单线程化线程池(SingleThreadExecutor)

    只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。不适合并发以及可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等。

Executors 的 4 个功能线程有如下弊端
  • FixedThreadPool 和 SingleThreadExecutor:

    主要问题是堆积的请求处理队列均采用 LinkedBlockingQueue,可能会耗费非常大的内存,甚至 OOM。

  • CachedThreadPool 和 ScheduledThreadPool

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

合理设置线程池参数
return new ThreadPoolExecutor(
                Runtime.getRuntime().availableProcessors() + 1,
                1024,
                1, TimeUnit.HOURS,
                new ArrayBlockingQueue<>(1024), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r);
            }
        },
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println("rejected");
                    }
                }
        );

结语

线程池帮我们提高对线程的管理和监控能力,本身也降低了因为创建线程和线程切换带来的内存资源和cpu资源的消耗,也提高了整体的响应速度。
但是线程池也需要我们自己通过ThreadPoolExecutor自己定义,拒绝直接适用Executors工具提供的线程池类。导致的线程不可控的问题。

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

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

相关文章

快速解决msvcp140.dll丢失问题的5个方法,轻松修复dll问题

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcp140.dll丢失”。那么&#xff0c;msvcp140.dll究竟是什么&#xff1f;它丢失需要怎么修复呢&#xff1f;本文将从多个角度对这一问题进行详细解析。 一、msvcp140.dll是什么 msvcp14…

C/C++输出整数 2020年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C输出整数 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C输出整数 2020年9月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 输入四个整数&#xff0c;把输入的第三、第四个整数输出…

Jmeter(十四):跨线程组传递jmeter变量及cookie的处理详解

setUp线程组 setUp thread group 一种特殊类型的线程组&#xff0c;用于在执行常规线程组之前执行一些必要的操作。 在 setup线程组下提到的线程行为与普通线程组完全相同。不同的是执行顺序--- 它会在普通线程组执行之前被触发&#xff1b; 应用场景举例&#xff1a; A、测…

21.7 Python 使用Request库

Request库可以用来发送各种HTTP请求&#xff0c;该框架的特点是简单易用&#xff0c;同时支持同步和异步请求&#xff0c;支持HTTP协议的各种方法和重定向。它还支持Cookie、HTTPS和认证等特性。 Request库的使用非常广泛&#xff0c;可以用于网络爬虫、API调用、网站测试等场景…

vue3 echarts实现k线

文章目录 echarts实现k线核心代码完整代码 echarts实现k线 安装 npm install echarts实现效果 没有添加成交量和均线 核心代码 引入echarts echartsData表示echarts数据 这样写是为了方便点击时间切换k线数据 window.addEventListener(“resize”, () > { chart.resi…

实习日常的点点滴滴记录(threadlocal知识概括和相关应用场景)------慢慢积累,厚积薄发(要学的东西还好多,加油!))(知识和实践的结合)

在通常的业务开发中&#xff0c;ThreadLocal 有两种典型的使用场景&#xff1a; 场景1&#xff1a; ThreadLocal 用作保存每个线程独享的对象&#xff0c;为每个线程都创建一个副本&#xff0c;这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本&#xff0c…

tinymce输入框怎么限制只输入空格或者回车时不能提交

项目场景: 项目相关背景: tinymce输入框只输入空格或者回车时提交的空数据毫无意义,所以需要限制一下 无意义的输入: 解决方案: 因为tinymce输入框传到后端的数据是代码形式,所以不能直接.trem,需要一步步的进行去除空格(空格分:‘ ’与‘ ’)与回车。 注意:空…

xFlight开源飞控之AT32F435计划

xFlight开源飞控之AT32F435计划 1. 源由2. 材料清单2.1 结构件2.2 动力件2.2 电子件2.3 天线2.4 附件 3. 固件准备4. 整机外观5. 问题汇总5.1 【已解决】iNav固件无法找到Baro芯片5.2 【已解决】正反电机问题5.3 【已解决】摄像头图像模糊5.4 【已解决】ESC 电机 bdshot异常5.5…

Ubuntu中使用yum命令出现错误提示:Command ‘yum‘ not found, did you mean:

Ubuntu中使用yum命令出现错误提示:Command ‘yum’ not found, did you mean: command ‘gum’ from snap gum (0.12.0) command ‘num’ from deb quickcal (2.4-1) command ‘yum4’ from deb nextgen-yum4 (4.5.2-6) command ‘uum’ from deb freewnn-jserver (1.1.1~a021…

ASEMI肖特基二极管MBR40200PT参数和作用详解

编辑-Z 肖特基二极管MBR40200PT是一种高效的电子元件&#xff0c;广泛应用于电源管理和功率控制领域。它具有低导通压降和快速恢复特性&#xff0c;能够在高频率和高温环境下稳定工作。 MBR40200PT采用了肖特基结构&#xff0c;该结构是由一个金属与一个半导体材料接触而形成的…

hdlbits系列verilog解答(向量5)-19

文章目录 一、问题描述二、verilog源码三、仿真结果一、问题描述 给定五个 1 位信号(a、b、c、d 和 e),计算 25 位输出向量中的所有 25 个成对一比特比较。如果要比较的两个位相等,则输出应为 1。 out[24] = ~a ^ a; // a == a, so out[24] is always 1. out[23] = ~a ^ b…

linux安装vscode vscode使用 创建项目并运行

下载 https://code.visualstudio.com/ 下载.deb文件 安装 假如文件被下载到了 /opt目录下 进入Opt目录&#xff0c;右键从当前目录打开终端。 输入下面的安装命令。 sudo apt-get install ./code_1.83.1-1696982868_amd64.deb 安装成功。 安装插件 使用c&#xff0c;必…

MyBatisPlus创建新的Mapper.xml映射文件而不使用框架自带的?

MyBatisPlus创建新的Mapper.xml映射文件而不使用框架自带的&#xff1f; 以后使用数据库框架的时候可以使用MyBatisPlus而不适用MyBatis&#xff0c;因为MyBatisPlus更为简便&#xff0c;像简单的增删改查操作&#xff0c;在MyBatisPlus中可以直接完成&#xff0c;不用写Mappe…

开发知识付费小程序的兴起:机会与挑战

近年来&#xff0c;随着移动互联网的快速发展&#xff0c;知识付费成为了一种备受欢迎的商业模式。知识付费小程序作为这一领域的新兴力量&#xff0c;正逐渐崭露头角。本文将探讨知识付费小程序的兴起、机会与挑战&#xff0c;以及这一趋势对于创业者和知识分享者的影响。 一、…

iOS开发-Lottie实现下拉刷新动画效果

iOS开发-Lottie实现下拉刷新动画效果 在开发过程中&#xff0c;有时候需要自定义下拉刷新控件&#xff0c;这里使用Lottie实现下拉刷新动画效果。 一、Lottie Lottie 是一个应用十分广泛动画库&#xff0c;适用于Android、iOS、Web、ReactNative、Windows的库&#xff0c;它…

洞察运营机会的数据分析利器

这套分析方法包括5个分析工具&#xff1a; 用“描述性统计”来快速了解数据的整体特点。用“变化分析”来寻找数据的问题和突破口。用“指标体系”来深度洞察变化背后的原因。用“相关性分析”来精确判断原因的影响程度。用“趋势预测”来科学预测未来数据的走势&#xff0c;

OpenLayers入门,OpenLayers从vue的assets资源路径加载geojson文件并解析数据叠加到地图上,以加载世界各国边界为例

专栏目录: OpenLayers入门教程汇总目录 前言 本章以加载世界各国边界的GeoJSON格式数据为例,讲解如何使用OpenLayers从vue的assets资源路径加载geojson文件并解析数据叠加到地图上。 二、依赖和使用 "ol": "^6.15.1"使用npm安装依赖npm install ol@…

我是这样保持精力充沛的

精力管理就好比是计算机的内存清理&#xff0c;你以为关掉一些程序就行了&#xff0c;结果你还是卡成翔。 我的现状 雷猴啊&#xff0c;我是一个临期程序员。打过几年工&#xff0c;被好几个同事问过我为什么精力这么旺盛。 这两年我大多数情况都是早上8点前就到公司*(原本9点上…

LVS-DR模式+keepalived+nginx+tomcat实现动静分离、负载均衡、高可用实验

实验条件&#xff1a; test2——20.0.0.20——主服务器——ipvsadm、keepalived服务 test3——20.0.0.30——备服务器——ipvsadm、keepalived服务 nginx5——20.0.0.51——后端真实服务器1&#xff08;tomcat的代理服务器&#xff09;——nginx服务 nginx6——20.0.0.61—…

buuctf_练[CISCN2019 华东南赛区]Web4

[CISCN2019 华东南赛区]Web4 文章目录 [CISCN2019 华东南赛区]Web4掌握知识解题思路代码分析正式解题 关键paylaod 掌握知识 ​ 根据url地址传参结构来判断php后端还是python后端&#xff1b;uuid.getnode()函数的了解&#xff0c;可以返回主机MAC地址十六进制&#xff1b;pyt…