多线程学习-线程池
目录
1.线程池的作用
2.线程池的实现
3.自定义创建线程池
1.线程池的作用
当我们使用Thread的实现类来创建线程并调用start运行线程时,这个线程只会使用一次并且执行的任务是固定的,等run方法中的代码执行完之后这个线程就会变成垃圾等待被回收掉。如果是使用实现Runnable接口或者使用实现Callable接口先创建一个任务类,再将任务传递给创建的线程,那这个线程虽然可以用来执行不同的任务,只需要将不同的任务类对象传递给这个线程即可,但仍旧无法解决线程使用的一次性问题。我们希望一个线程不仅是能够通用的,而且还是能够复用的,这就需要线程池来帮忙。
线程池顾名思义就是一个存放线程的池子,当执行一个任务需要线程时就从里面取出一个,用完之后再还回线程池。其特点是:
- 初始状态下线程池是空的,里面没有线程,需要线程时才会创建线程。
- 当线程池创建的线程数还没有达到线程池的容量时,如果有任务需要一个线程但线程池中没有空闲线程,线程池会重新创建一个线程放到线程池中。
- 当线程池创建的线程数达到线程池的容量时,即使线程池中没有空闲线程也不会继续创建,这时那些需要线程的任务会进入一个等待队列,等到其他任务归还线程后才能根据先到先得的原则获取线程。
- 当任务执行完毕后所使用的线程会归还给线程池,此时这个线程又变成空闲线程。
2.线程池的实现
线程池有两种,一种是无限线程池,理论上线程池的容量是无限的,实际上其最大容量为int的最大范围,但由于实际生活中创建一个int范围的线程基本上不能实现,并且也无法同时运行这么多的线程,所以可以说是无限的;另一种则是有限线程池,在创建时需要指定容量。

代码实现:
//实现Runnable接口创建的任务类
public class MyRun implements Runnable{@Overridepublic void run() {for(int i=0;i<100;i++)System.out.println(Thread.currentThread().getName()+"正在执行MyRun的任务,输出"+i);}
}//实现Callable接口创建的任务类
public class MyCall implements Callable<String> {@Overridepublic String call() throws Exception {return Thread.currentThread().getName()+"正在执行MyCall的任务";}
}//使用线程池
public class Main {public static void main(String[] args) throws ExecutionException, InterruptedException {//无限线程池的创建ExecutorService pool= Executors.newCachedThreadPool();//有限线程池的创建ExecutorService pool1=Executors.newFixedThreadPool(3);//创建两种类型的任务类Runnable r=new MyRun();Callable<String> c=new MyCall();FutureTask<String> ft=new FutureTask<String>(c);//提交任务,提交之后就会向线程池申请线程并执行任务pool1.submit(r);//由于Callable类型的任务的返回值需要FutureTask管理,所以提交的是ftpool1.submit(ft);System.out.println(ft.get());//销毁线程池pool1.shutdown();pool.shutdown();}
}
运行结果:


这里可以发现线程池创建的线程是以“pool-线程池编号-thread-线程编号”来命名的。线程池和所创建的线程的编号都是从1开始,可以查看源码:


在上面的例子中,有限线程池设置的容量为3,但只提交了两个任务,所以不会出现有任务获取不到线程而进入等待队列的情况。
下面测试进入等待队列的情况,提交4个Runnable类型的任务,因为一个任务要循环100次输出耗时较长,可以在提交第四个任务时保证线程池中没有空闲线程,然后让其进入等待队列。利用Debug来查看是否有任务进入了等待队列,先准备好测试代码并加上断点:

开始调试,线程池在创建后可以看到它的属性:
下面执行提交第一个任务:

提交第二个任务:

提交第三个任务:

提交第四个任务:

补充:在实际情况下服务器通常都是一直工作,所以线程池会一直被使用,就没有必要销毁掉,所以一般情况下不会用到销毁操作。
3.自定义创建线程池
一个线程池所创建的线程分为核心线程和临时线程,核心线程则是在创建后会一直存在,直到线程池关闭,而临时线程则是当提交的任务过多时应急使用的,临时线程也会正常工作,但在工作结束后如果在一定时间内没有其他任务,则会被销毁。要注意的是,只有当等待队列的任务占满了整个队列,后面再提交任务时才会创建临时线程,并且创建的临时线程处理的任务并不是等待队列中的任务,而是队列满后后面再提交的任务。如果创建的临时线程达到最大数量,此时仍有任务被提交时就需要选择一种应对策略来处理后面提交的任务。
自定义线程池需要设置以下七种参数:
- 允许创建的最大核心线程数量(不能小于0)
- 允许创建的最大线程数量(不能小于0且必须>=核心线程数量,最大线程数量减去最大核心线程数量就是可以创建的最大临时线程数量)
- 临时线程的最大空闲时间1(设置时间的值,不能小于0)
- 临时线程的最大空闲时间2(设置时间的单位,使用TimeUnit指定)
- 等待队列(其实就是一个阻塞队列,不能为null)
- 创建线程的工厂(也就是怎样创建一个线程,不能为null)
- 应对策略(一共有四种策略,四选一,一般选择第一个,不能为null)

自定义创建线程池代码实现:
//创建自定义线程池
ThreadPoolExecutor pool2=new ThreadPoolExecutor(3, //设置核心线程的最大创建数量5, //设置最大线程数量(最大临时线程数量也就是5-3=2)60, //设置临时线程的最大空闲时间的值部分TimeUnit.SECONDS, //设置临时线程的最大空闲时间的单位部分new ArrayBlockingQueue<>(5), //加入阻塞队列作为等待队列Executors.defaultThreadFactory(), //使用java提供的线程池默认创建线程的工厂作为创建线程的工厂即可new ThreadPoolExecutor.AbortPolicy() //选择第一种应对策略,AbortPolicy是一个内部类);
使用方式和普通线程一样,提交任务用submit,销毁用shutdown。
补充内部类:内部类是在一个类中定义另一个类。当这个类需要依附于另一个类但这个类本身也是独立的一部分时可以将这个类创建为其他类的内部类,就比如发动机和汽车,发动机需要依附于汽车才能发挥作用,但发动机本身又是独立的一部分。
那线程池的最大线程数量是不是越大越好呢?其实不然,线程池的最大线程数量通常是按照规定来的,这取决于开发的项目是CPU密集型还是I/O密集型的。
CPU密集型也就是所开发的项目需要进行的运算偏多,而执行运算操作就要用到CPU。这种项目所需的最大线程数量应当设置为:最大并行数+1。
所谓最大并行数就是看CPU能最多能分给java多少线程,通常说CPU是多少核多少线程的,核数就是这个CPU有多少大脑,多少线程就是这个CPU有多少只手,每只手对应一个线程,但不一定所有的线程都可以让java调度,我们可以通过下面的代码查看可以分给java的最大线程数:
//查看CPU能分给java的最大线程数
int num=Runtime.getRuntime().availableProcessors();
System.out.println(num);
可以分配给java的最大线程数就是最大并行数,至于要加1是为了当已经创建了的某个线程出现问题时可以利用这个多出来的线程继续工作, 尽可能地将CPU利用率最大化。
I/O密集型就是开发的项目中I/O操作比较多,现在大多数项目都是I/O密集型的。这种情况下线程池的最大线程数量可以设置为:最大并行数*期望CPU的利用率*(总的运行时间/CPU的运行时间)。通常情况下我们希望CPU的利用率越高越好,所以可以设置为100%,最大并行数在CPU密集型部分介绍过了,那什么是CPU的运行时间呢?
比如要执行读取文件中的两个整数并相加的操作,这个操作分为两部分,从文件中读取数据的部分没有用到CPU,而后面的相加部分属于运算,就需要CPU了。所以在这个例子中,CPU的运行时间就是后面相加所需的时间。在实际项目中可以使用thread dump工具来测总的运行时间和CPU的运行时间。
相关文章:
多线程学习-线程池
目录 1.线程池的作用 2.线程池的实现 3.自定义创建线程池 1.线程池的作用 当我们使用Thread的实现类来创建线程并调用start运行线程时,这个线程只会使用一次并且执行的任务是固定的,等run方法中的代码执行完之后这个线程就会变成垃圾等待被回收掉。如…...
Linux第4课 Linux的基本操作
文章目录 Linux第4课 Linux的基本操作一、图形界面介绍二、终端界面介绍 Linux第4课 Linux的基本操作 一、图形界面介绍 本节以Ubuntu系统的GUI为例进行说明,Linux其他版本可自行网搜。 图形系统进入后,左侧黄框内为菜单栏,右侧为桌面&…...
堆排序解读
在算法世界中,排序算法一直是一个热门话题。推排序(Heap Sort)作为一种基于堆这种数据结构的有效排序方法,因其时间复杂度稳定且空间复杂度低而备受青睐。本文将深入探讨推排序的原理、实现方式,以及它在实际应用中的价…...
docker + miniconda + python 环境安装与迁移(详细版)
本文主要列出从安装dockerpython环境到迁移环境的整体步骤。windows与linux之间进行测试。 简化版可以参考:docker miniconda python 环境安装与迁移(简化版)-CSDN博客 目录 一、docker 安装和测试 二、docker中拉取minicondaÿ…...
蓝桥杯刷题第八天(dp专题)
这道题有点像小学奥数题,解题的关键主要是: 有2种走法固走到第i级阶梯,可以通过计算走到第i-1级和第i-2级的走法和,可以初始化走到第1级楼梯和走到第2级楼梯。分别为f[1]1;f[2]1(11)1(2)2.然后就可以循环遍历到后面的状态。 f[i…...
【WEEK6】 【DAY1】DQL查询数据-第一部分【中文版】
2024.4.1 Monday 目录 4.DQL查询数据(重点!)4.1.Data Query Language查询数据语言4.2.SELECT4.2.1.语法4.2.2.实践4.2.2.1.查询字段 SELECT 字段/* FROM 表查询全部的某某查询指定字段 4.2.2.2.给查询结果或者查询的这个表起别名(…...
Linux:权限篇
文章目录 前言1.用户2.文件的权限管理2.1 修改文件的权限2.2 修改文件的拥有者2.3 修改文件的所属组 3.file指令4.umask指令4.目录的权限管理总结 前言 Linux权限在两个地方有所体现,一种是使用用户:分为root超级用户员与普通用户。另一个是体现在文件的…...
Lua热更新(xlua)
发现错误时检查是否:冒号调用 只需要导入asset文件夹下的Plugins和Xlua这两个文件即可,别的不用导入 生成代码 和清空代码 C#调用lua using Xlua; 需要引入命名空间 解析器里面执行lua语法 lua解析器 LuaEnv 单引号是为了避免引号冲突 第二个参数是报错时显示什么提示…...
并查集(基础+带权以及可撤销并查集后期更新)
并查集 并查集是一种图形数据结构,用于存储图中结点的连通关系。 每个结点有一个父亲,可以理解为“一只伸出去的手”,会指向另一个点,初始时指向自己。一个点的根节点是该点的父亲的父亲的..的父亲,直到某个点的父亲…...
基于 Java 的数据结构和算法 (不定期更新)
JavaIsBestLang 数据结构 Collection 是 Java 中的接口,被多个泛型容器接口所实现。在这里,Collection 是指代存放对象类型的数据结构。 ArrayList 函数名功能size()返回 this 的长度add(Integer val)在 this 尾部插入一个元素add(int idx, Integer …...
考研回忆录【二本->211】
备考时长差不多快一年半,从22年的11月底开始陆陆续续地准备考研,因为开始的早所以整个备考过程显得压力不是很大,中途还去一些地方旅游,我不喜欢把自己绷得太紧。虽然考的不是很好,考完我甚至都没准备复试,…...
【XCPC笔记】2023 (ICPC) Jiangxi Provincial Contest——ABCIJKL 做题记录
补题 赛后gym练习及补题,gym链接:2023 (ICPC) Jiangxi Provincial Contest – Official Contest 另外,D题我也打算找机会学习写下,C题的博弈论还需要好好理解,感觉都是比较有趣的数学问题 补题顺序如下 补题L [Zhang …...
猫头虎分享已解决Bug || **URLError (URL错误)** 全方位解析
博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通鸿蒙》 …...
如何使用极狐GitLab 启用自动备份功能
本文作者:徐晓伟 GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 本文主要讲述了如何极狐GitLab 自…...
HTML/XML转义字符对照
特殊字符转义表 字符十进制转义字符""&&<<<>>>不断开空格(non-breaking space) 最常用的转义字符列表 显示说明实体名称十进制编号半方大的空白 全方大的空白 不断行的空白格 <小于<<>大于&g…...
设计模式:组合模式示例
组合模式的典型例子通常涉及到树形结构的处理,下面是几个形象且易于理解的例子: 文件系统 在文件系统中,目录可以包含文件或者其他目录,但是从用户的角度来看,目录和文件都可以被“打开”或者“获取大小”。这里的目…...
普通情况和高并发时,Redis缓存和数据库怎么保持一致?
普通情况和高并发时,Redis缓存和数据库怎么保持一致? 普通情况思路 高并发时思路 Q:缓存和数据库怎么保持一致? A:绝对不可能保持一致的,在实际业务开发中,有一些方案可以做取舍。 实际业务中&a…...
Django -- 自动化测试
概述 测试是一种例行的、不可缺失的工作,用于检查你的程序是否符合预期。 测试可以划分为不同的级别。一些测试可能专注于小细节(比如某一个模型的方法是否会返回预期的值?), 一些测试则专注于检查软件的整体运行是否…...
NodeJS 在Windows / Mac 上实现多版本控制
NodeJS 的多版本控制 本文介绍一下在 windows/MacOS 上 如何 切换和使用多个版本的 NodeJS。 Windows 本小节介绍一下在windows上管理不同版本的NodeJS。 nvm-windows 工具 nvm-windows 是在 windows 上管理 NodeJS 版本的一个工具。 它可以很方便的 下载、移除、查看、切…...
Web3 游戏周报(3.24-3.30)
【3.24-3.30】Web3 游戏行业动态: Web3 开发平台 Mirror World 在 Solana 上推出首个游戏 rollup 链 NFT 卡牌游戏 Parallel 完成 3,500 万美元融资,Solana Ventures 等参投 加密游戏开发公司 Gunzilla Games 完成 3,000 万美元融资 Telegram 游戏 No…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
路由基础-路由表
本篇将会向读者介绍路由的基本概念。 前言 在一个典型的数据通信网络中,往往存在多个不同的IP网段,数据在不同的IP网段之间交互是需要借助三层设备的,这些设备具备路由能力,能够实现数据的跨网段转发。 路由是数据通信网络中最基…...
SQL注入篇-sqlmap的配置和使用
在之前的皮卡丘靶场第五期SQL注入的内容中我们谈到了sqlmap,但是由于很多朋友看不了解命令行格式,所以是纯手动获取数据库信息的 接下来我们就用sqlmap来进行皮卡丘靶场的sql注入学习,链接:https://wwhc.lanzoue.com/ifJY32ybh6vc…...
