多线程(初阶九:线程池)

目录

一、线程池的由来

二、线程池的简单介绍

1、ThreadPoolExecutor类

(1)核心线程数和最大线程数:

(2)保持存活时间和存活时间的单位

(3)放任务的队列

(4)线程工厂

(5)拒绝策略

2、Executors类

3、线程池的执行流程

4、讨论线程池中创建多少线程合适

三、线程池的模拟实现

(1)阻塞队列:存放要执行的任务

(2)submit方法:添加任务的方法,任务添加到队列中

(3)构造方法:指定创建多少个线程,线程在这个构造方法中都创建好了

(4)存放线程的链表:每创建一个线程都放进链表中,这样也能让我们找到某个线程

(5)最终代码( + 测试用例)

都看到这了,点个赞再走吧,谢谢谢谢谢


一、线程池的由来

最开始,进程可以解决并发编程的问题,但是这个代价太大了,于是引入了 “轻量级进程” :线程

线程也能解决并发编程的问题,而且线程的开销比进程要小的多,但是线程如果太多了,创建销毁线程的频率进一步提高,此时的线程创建销毁的开销就不能忽视了。

为了解决上述问题,大佬们给出了两个解决方案:

(1)引入 “轻量级线程”:纤程 / 协程

        协程的本质是程序猿在用户态代码中进行调度,不是靠内核的调度器调度的,这样就节省了很多开销;协程是在用户代码中,基于线程封装出来的,可能是N个协程对应1个线程,也可能是N个协程对应M个线程。

(2)引入 “线程池”

        线程池的概念:创建一个线程,这个线程执行完,不会把这个线程给销毁,而是把这个线程放到线程池中,当我们需要用这个线程的时候,再从线程池中拿,不需要的时候,就放在线程池中,并不会销毁它;这样,就省去了频繁的创建销毁线程了。

为啥从线程池中取线程 比 从系统中申请线程的创建更高效呢?

        举个栗子:

假设在银行场景中,滑稽老铁要去这个银行办理一个业务,一般银行中大堂有复印机;这时,滑稽老铁没有带身份证复印件,此时滑稽老铁要去搞到身份证复印件,有两个选择,其一选择:把身份证给柜员,让柜员帮滑稽老铁复印,但是这个操作是不可控的,可能这个柜员中途被老板安排了其他活,那这个时候,就不能帮滑稽老铁复印身份证了,要等忙完老板安排的活,再帮滑稽老铁复印身份证;其二选择:滑稽老铁自己去大堂中复印身份证,这样就比较可控了,滑稽老铁可以很快的去到打印机,立马复印出来,再去办理他的业务。如图:

这里的大堂就是用户态,柜台就是内核态,从线程池中取线程,是纯用户态代码(可控)                                                                                   通过系统申请创建线程,需要内核完成(不可控)


二、线程池的简单介绍

1、ThreadPoolExecutor类

ThreadPoolExecutor参数最多的构造方法,明白了这个构造方法,其他构造方法的参数也就都明白了,如图:

(1)核心线程数和最大线程数:

corePoolSize核心线程数:正式员工线程

maximumPoolSize最大线程数:正式员工线程 + 实习员工线程

        举个栗子:一个公司中有10个正式员工,这10个正式员工是不能随便开除的,当这10个正式员工忙不过来的时候,公司为了降低成本,会招聘实习员工,而这几个实习员工是可以随便开除的,当公司稳定一段时间不忙后,就会开除几个实习员工。

(2)保持存活时间和存活时间的单位

KeepAliveTime保持存活时间:实习生线程允许摸鱼的最大时间

unit存活时间的单位:可以是hour 、 min 、 s 、 ms

(3)放任务的队列

和定时器类似,线程池中也可以持有多个任务,要执行的任务,使用Runnable来描述任务。

(4)线程工厂

通过这个工厂类创建线程对象(Thread对象),工厂类里面有方法封装了new Thread的操作,同时给Thread设置了一些属性,我们想要创建线程的时候可以直接使用工厂类的方法创建。

举个栗子:

描述一个点,可以用二维坐标和极坐标来表示:二维坐标:(x,y) 极坐标:(r,α)

这里,通过new一个类来得到一个点,这个类里有两个构造方法,参数分别是(double x,double y),(double r,double α),那么这两个构造方法的参数类型都一样,构成不了重载,如图:

那我们就改方法名不就好了,在使用static修饰,通过不同的方法名获取类,在方法里new一个类,里面设置一些参数,再返回这个类,如图:

这样的的类,就称为工厂类,工厂类里面得到类的方法就称为工厂方法。

总的来说,通过静态方法new了一个对象,在这个静态方法设置不同的属性,构造对象的过程,就称为工厂模式。

(5)拒绝策略

在线程池中有一个阻塞队列,这个队列容纳线程有上限,如果这个任务队列满了,这时有往再添加任务,会发生啥事?

这就引出了拒绝策略,在线程池中,会有四个拒绝策略,如图:

第一个策略:会直接抛出一个异常,这样,旧的任务执行不了,新的任务也执行不了

第二个策略:把新的任务丢给添加任务队列的线程执行,不给入队列,同时旧的任务依然在执行

第三个策略:把最旧的任务丢弃,添加最新的任务进来

第四个策略:直接把新的任务丢弃了,不执行新的任务,旧的任务会继续执行

2、Executors类

ThreadPoolExecutor类本身使用起来比较复杂,标准库给我们提供了另一个版本:把ThreadPoolExecutor封装了一下,这个类就是Executor类,通过这个类创建出不同的线程池对象,在其内部,已经把ThreadPoolExecutor创建好了,并且设置了一些参数。

Executor的简单使用,其中主要方法有一下4个,如图:

我们创建一个固定线程数目的线程池,再往里添加任务

代码:

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(4);
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello word");
            }
        });
    }
}

执行结果:

那啥时候使用Executor,啥时候使用ThreadPoolExecutor呢?

网上流传了 阿里巴巴java开发编程规范,里面写了不建议使用Executor,而且一定要使用ThreadPoolExecutor,里面说用ThreadPoolExecutor意味着一切都在掌控之中,可以避免一些不必要的因素;我们可以作为参考,不必奉为金科玉律,他们两各有各的优缺点,这也要以以后入职的公司编程规范为准。

3、线程池的执行流程

(1)当有有任务要让线程池里面的线程执行时,会比较工作线程数和核心线程数,                     如果工作线程数 < 核心线程数,则会直接安排线程去执行这个任务。

(2)当工作线程数 > 核心线程数,即线程池中的核心线程数满了,会添加进阻塞任务队列中,天气任务队列前也会判断任务队列是不是空,是空就阻塞等待。

(3)如果线程池中的存活线程数 == 核心线程数,并且阻塞任务队列也满了,此时会判断是否到了最大线程数:maximumPoolSize,如果没有到达,就会让非核心线程去执行这个任务。

(4)如果当前线程数到达了最大线程数,则会执行拒绝策略。

4、讨论线程池中创建多少线程合适

假设一个进程中,所有线程都是cpu密集型,这时每个线程的工作都是在cpu上执行的,此时,线程池中的数目就不应该超过N(cpu的逻辑核心线程数)

如果一个进程中,所有线程都是IO密集型的,这时每个线程的大部分工作都是在等待IO,此时,线程池中的数目就可以远远超过N(cpu的逻辑核心线程数)

上述情况都是极端情况,实际上一个进程中的线程,有cpu密集型的,也有IO密集型的,只是比例不同。由于程序的复杂性,很难直接对线程池进行预估,更准确的做法是通过实验 / 测试的方法,找出合适的线程数目;也就是尝试给线程池设定不同的线程,对不同线程情况线程池执行的效率、性能进行评估,找到合适的线程数目。


三、线程池的模拟实现

模拟线程数目固定的线程池

(1)阻塞队列:存放要执行的任务

代码:

//阻塞队列:存放要执行的任务
private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(5);

(2)submit方法:添加任务的方法,任务添加到队列中

代码:

//提供submit方法,可以添加任务
public void submit(Runnable runnable) throws InterruptedException {
    queue.put(runnable);
}

(3)构造方法:指定创建多少个线程,线程在这个构造方法中都创建好了

public MyThreadPoolExecutor(int n) {
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                while (true) {
                    try {
                        //取出一个任务
                        Runnable runnable = queue.take();
                        //执行任务
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
            list.add(t);
        }
    }

解析:线程里面,取出一个任务就执行这个任务,如果队列里没有任务,就会阻塞等待,等有任务,再执行任务,如此循环往复;每创建一个线程,都要放进链表中,也要记得start。

(4)存放线程的链表:每创建一个线程都放进链表中,这样也能让我们找到某个线程

代码:

//存放线程的链表
List<Thread> list = new ArrayList<>();

(5)最终代码( + 测试用例)

class MyThreadPoolExecutor {
    //存放线程的链表
    List<Thread> list = new ArrayList<>();
    //阻塞队列:存放要执行的任务
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(5);
    //提供submit方法,可以添加任务
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
    public MyThreadPoolExecutor(int n) {
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                while (true) {
                    try {
                        //取出一个任务
                        Runnable runnable = queue.take();
                        //执行任务
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
            list.add(t);
        }
    }
}
public class MyThreadPoolExecutorTest {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPoolExecutor myThreadPoolExecutor = new MyThreadPoolExecutor(4);
        for (int i = 0; i < 1000; i++) {
            //变量捕获
            int n = i;
            myThreadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行任务:" + n + ",当前线程:" + Thread.currentThread().getName());
                }
            });
        }
    }
}

测试用例:指定线程池的数目为4个线程,添加1000次任务到阻塞队列中,让着4个线程从阻塞队列中拿任务,再执行任务,任务:打印0~1000,并显示是哪个线程打印的;

注意:这里我们打印那里我们不能直接放 i ,这里涉及到变量捕获,不能编译通过,但他们可以在循环里创建一个变量,把 i 的值赋值给这个变量,再打印 n,这样每循环一次,都会创建一个成员变量,这个成员变量也不会变,预期也和我们想要预期效果一样。

执行结果,如图:

可以看到,并不是顺序打印1~1000的,因为不同线程拿到任务的时机不同,多线程执行的顺序也是随机的。


都看到这了,点个赞再走吧,谢谢谢谢谢

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

我的网站服务器被入侵了该怎么办?

最近有用户咨询到德迅云安全&#xff0c;说自己再用的网站服务器遇到了入侵情况&#xff0c;询问该怎么处理入侵问题&#xff0c;有什么安全方案可以解决服务器被入侵的问题。下面&#xff0c;我们就来简单讲下服务器遇到入侵了&#xff0c;该从哪方面入手处理&#xff0c;在预…

华清远见嵌入式学习——QT——作业3

作业要求: 代码效果图&#xff1a; 登录成功并跳转页面 登录失败 关闭 代码&#xff1a; 第一页面头文件&#xff1a; #ifndef LOGIN_H #define LOGIN_H#include <QWidget> #include <QMessageBox>QT_BEGIN_NAMESPACE namespace Ui { class Login; } QT_END_NAME…

Hbase2.5.5分布式部署安装记录

文章目录 1 环境准备1.1 节点部署情况1.2 安装说明 2 Hbase安装过程Step1&#xff1a;Step2:Step3:Step4&#xff1a; 3 Web UI检查状态并测试3.1 Web UI3.2 创建测试命名空间 1 环境准备 1.1 节点部署情况 Hadoop11&#xff1a;Hadoop3.1.4 、 zookeeper3.4.6、jdk8 Hadoop1…

OpenCV | sift函数使用——得到特征点

scale invariant feature transform (sift) 图像尺度空间 在一定的范围内&#xff0c;无论物体是大还是小&#xff0c;人眼都可以分辨出来&#xff0c;然而计算机要有相同的能力却很难&#xff0c;所以要让机器能够对物体在不同尺度下有一个统一的认知&#xff0c; 就需要考虑…

分页设计(平时在表下面的栏框,有首页 | 上一页 | 下一页 | 尾页),下面代码带你实现

分页设计的本质就是&#xff0c;分页查询&#xff0c;就是SQL语句当中的(select * from ? limit ? , &#xff1f;&#xff09;,这里第一个&#xff1f;是所分页的那张表 &#xff0c;第二个&#xff1f;从哪条开始&#xff0c;第三个&#xff1f;是在页面上想让这张表出现几…

Linux系统vim,gcc,g++工具使用及环境配置,动静态库的概念及使用

Linux系统vim&#xff0c;gcc&#xff0c;g工具使用及环境配置&#xff0c;动静态库的概念及使用 1. Linux编辑器-vim的使用1.1 vim的基本概念1.2vim的基本操作1.3vim正常模式命令集1.4vim末端模式命令集1.5简单的vim配置 2.Linux编译器-gcc/g的使用2.1 准备阶段2.2gcc的使用2.…

Redis持久化机制 RDB 和 AOF 的选择

目录 一、Redis 的持久化 二、Redis 的持久化方式 Redis 提供了两种持久化的方式&#xff1a; RDB 介绍 RDB 的触发方式&#xff1a; AOF介绍 三、RDB 和 AOF 的选择 RDB 和 AOF 对比 1. 数据格式&#xff1a; 2. 恢复速度&#xff1a; 3. 数据丢失 4. 文件大小&…

这七款网工在线画拓扑工具,绝了!

你们好&#xff0c;我的网工朋友。 画拓扑图&#xff0c;绝对是网络工程师的基操。 上次给你来了篇手把手教你绘制拓扑图的好文&#xff0c;还没看过的先去看啊&#xff1a;《网络拓扑图怎么画最好&#xff1f;》。 关于画拓扑的工具&#xff0c;那就多了&#xff0c;直接用…

什么是 web 组态?web 组态与传统组态的区别是什么?

组态软件是一种用于控制和监控各种设备的软件&#xff0c;也是指在自动控制系统监控层一级的软件平台和开发环境。这类软件实际上也是一种通过灵活的组态方式&#xff0c;为用户提供快速构建工业自动控制系统监控功能的、通用层次的软件工具。通常用于工业控制&#xff0c;自动…

c++时间转换

获取当前时间字符串 std::string GetFormatTime() {time_t currentTime;time(&currentTime);tm* t_tm localtime(&currentTime);char formatTime[64] {0};snprintf(formatTime, 64, "%04d-%02d-%02d %02d:%02d:%02d", t_tm->tm_year 1900,t_tm->tm…

Python从入门到精通五:Python函数

函数介绍 学习目标&#xff1a; 快速体验函数的使用了解函数的作用 函数&#xff1a;是组织好的&#xff0c;可重复使用的&#xff0c;用来实现特定功能的代码段。 我们使用过的&#xff1a;input()、print()、str()、int()等都是Python的内置函数。 为什么要学习、使用函…

2023年团体程序设计天梯赛——总决赛题

F-L1-1 最好的文档 有一位软件工程师说过一句很有道理的话&#xff1a;“Good code is its own best documentation.”&#xff08;好代码本身就是最好的文档&#xff09;。本题就请你直接在屏幕上输出这句话。 输入格式&#xff1a; 本题没有输入。 输出格式&#xff1a; 在一…

使用 PyTorch FSDP 微调 Llama 2 70B

通过本文&#xff0c;你将了解如何使用 PyTorch FSDP 及相关最佳实践微调 Llama 2 70B。在此过程中&#xff0c;我们主要会用到 Hugging Face Transformers、Accelerate 和 TRL 库。我们还将展示如何在 SLURM 中使用 Accelerate。 完全分片数据并行 (Fully Sharded Data Paral…

java--集合基础

1.集合和数组的特点对比 集合类的特点 提供一种存储空间可变的存储模型&#xff0c;存储的数据容量可以发生改变 集合和数组的区别 共同点&#xff1a;都是存储数据的容器 不同点&#xff1a;数组的容量是固定的&#xff0c;集合的容量是可变的 2.ArrayList集合 ArrayLi…

解决 Element-ui中 表格(Table)使用 v-if 条件切换后,表格的列的筛选不显示了

解决方法 在每个需要使用 v-if 或 v-else 的 el-table-column 上增加 key 作为唯一标识&#xff0c;这样渲染的时候就不会因为复用原则导致列数据混乱了。关于key值&#xff0c;一般习惯使用字段名&#xff0c;也可随机生成一个值&#xff0c;只要具有唯一性就可以。

Centos7云服务器上安装cobalt_strike_4.7。附cobalt_strike_4.7安装包

环境这里是阿里的一台Centos7系统。 开始安装之前首先要确保自己安装了java11及以上环境。 安装java11步骤&#xff1a; sudo yum update sudo yum install java-11-openjdk-devel把服务器端&#xff08;CS工具分服务器端和客户端&#xff09;的CS安装到服务器上后给目录下的…

mysql原理--B+树索引的使用

1.索引的代价 在介绍如何更好的使用索引之前先要了解一下使用这玩意儿的代价&#xff0c;它在空间和时间上都会拖后腿&#xff1a; (1). 空间上的代价 这个是显而易见的&#xff0c;每建立一个索引都要为它建立一棵 B 树&#xff0c;每一棵 B 树的每一个节点都是一个数据页&…

Proxmark3 Easy救砖-20231209

事情是这样的&#xff0c;在淘宝买了个PM3&#xff0c;拿到手后刷固件的&#xff0c;一不小心刷成砖头了&#xff0c;现象就是四个灯全亮&#xff0c;插上电脑USB不识别。问商家他也不太懂&#xff0c;也是个半吊子技术&#xff0c;远程给我刷机搞了半天也没有搞定&#xff0c;…

Softmax回归

一、Softmax回归关键思想 1、回归问题和分类问题的区别 Softmax回归虽然叫“回归”&#xff0c;但是它本质是一个分类问题。回归是估计一个连续值&#xff0c;而分类是预测一个离散类别。 2、Softmax回归模型 Softmax回归跟线性回归一样将输入特征与权重做线性叠加。与线性回归…

排序算法:【冒泡排序】、逻辑运算符not用法、解释if not tag:

注意&#xff1a; 1、排序&#xff1a;将一组无序序列&#xff0c;调整为有序的序列。所谓有序&#xff0c;就是说&#xff0c;要么升序要么降序。 2、列表排序&#xff1a;将无序列表变成有序列表。 3、列表这个类里&#xff0c;内置排序方法&#xff1a;sort( )&#xff0…