【多线程-从零开始-玖】内核态,用户态,线程池的参数、使用方法详解

news2024/9/19 10:36:25

文章目录

  • 1. 为什么会有线程池
  • 2. 内核态和用户态
    • 2.1 场景构造
  • 3. 标准库的线程池
    • 3.1 构造方法的参数
      • 3.1.1 核心线程数和最大线程数
      • 3.1.2 非核心线程允许摸鱼的最大时间
      • 3.1.3 工作队列(阻塞队列)
      • 3.1.4 线程工厂
        • 工厂设计模式
      • 3.1.5 拒绝策略
        • 四种拒绝策略
    • 3.2 线程池的使用
  • 4. 线程池的模拟实现

1. 为什么会有线程池

“池”这种思想,本质上就是能提高程序的效率

最初引入线程,就是因为进程太重了,频繁创建、销毁进程,开销比较大

  • “大/小”是相对的,随着业务上对于性能要求越来越高,对应的,线程穿件/销毁的频次越来越多
  • 此时,线程创建和销毁的开销就变得比较明显,无法忽略不计了
    线程池就是解决上述问题的常见方案
  • 把线程提前从系统中申请好,放到一个地方,后面需要使用线程的时候直接从这个地方取,而不是从系统中重新申请
  • 线程用完之后,也是还回到刚才这个地方

2. 内核态和用户态

为啥从线程池里面取线程,比从系统申请来的更高效呢?

  • 内核态 & 用户态,操作系统中的概念
  • 操作系统=操作系统内核+操作系统配套的应用程序
    • 内核是操作系统的核心功能部分,负责完成一个操作系统的核心操作。向下要管理各种硬件资源(包括调用驱动程序,操控各种硬件设备);向上要给这些应用程序提供一个稳定的运行环境,给他们一些对应的 API 来完成相关的操作

2.1 场景构造

image.png|630
在银行里,你需要办理取款业务,就需要来到柜台前,给柜员说清楚你的需求,然后柜员再给你在后面的保险柜里面取钱(你是无法直接跨过柜台,自己取钱的)

  • 柜台内部——操作系统内核
  • 柜台外部——用户
    像我们平时执行的应用程序(CCtalk、画图板…),就是应用态的应用程序,而操作系统内核,提供了一些相关的系统 API 来给这些应用程序进行使用

比如执行一个 println 操作

  • 应用程序来到台前,给内核里面的人说:我现在要往控制台打印一个什么什么东西,你用空帮我弄一下呗
  • 然后内核里面的人就把数据拿到,开始操作显示器、控制台,完成这里的打印操作
    我们进行的很多操作,都是用户态的应用程序和操作系统内核(内核态)的逻辑相互配合,来完成工作

对应的,平时执行的很多代码逻辑,都是要用户态的代码和内核态的代码配合来完成的


应用程序有很多,这些应用程序都是由内核同一负责管理和服务,内核里的工作就可能非常繁忙,进而提交给内核的要做的任务可能是不可控的

比如说你要办一张银行卡
你来到了银行柜台前,但你没有身份证复印件,柜员说你有两个选择:

  1. 自己去旁边的自助复印机进行处理
  2. 等他帮你处理,但要稍等一会
  1. 如果选择自助复印,我就会立即去到复印机那里,立即开始复印,拿到复印件后立即回到窗口。整个过程是连贯的,是可控的效率就比较高
  2. 如果选择柜员帮忙,那么他就可能拿着身份证,跑到柜台后面,消失了,但什么时候能退回来,这就不知道了。因为他跑到后面后可能先得上个厕所、回个消息、玩下手机、吃点东西… 这整个过程是不可控的,因此效率比较低

  • 从系统创建线程,就相当与是让银行的人给我复印
    • 这样的逻辑就是调用系统 API,由系统内核执行一系列逻辑来完成这个过程
  • 直接从线程池里面来取,这就相当于是自助复印
    • 整个过程都是纯用户态代码,都是咱们自己控制的,整个过程更可控,效率更高

因此,我们通常认为,纯用户态操作,比经过内核的操作效率更高

3. 标准库的线程池

  • 标准库提供了类— ThreadPoolExecutor(构造方法提供了很多参数)

3.1 构造方法的参数

image.png

3.1.1 核心线程数和最大线程数

image.png|479
此线程池,可以支持“扩容”
某个线程初识情况下,可能有 M 个线程,实际使用中发现 M 不太够用,就会自动增加 M 的个数
因为 CPU 上的核心数量是有限的,所以线程不能无限扩容

  • 在 Java 标准库的线程池中,把里面的线程分为了两类
    • 核心线程

      • 最少有多少个线程
      • 始终存在于线程池内部
    • 非核心线程

      • 线程扩容的时候新增的
      • 繁忙的时候被创建出来,空闲了就会把这些线程真正的释放掉
  • { 核心线程数 + 非核心线程数 } m a x = 最大线程数 \{核心线程数+非核心线程数\}_{max}=最大线程数 {核心线程数+非核心线程数}max=最大线程数

3.1.2 非核心线程允许摸鱼的最大时间

image.png|464

非核心线程会在线程空闲空闲多久后会被销毁

比如:
公司招了个实习生,某一天,实习生没活干,但公司不会把他裁了,要是明天有活干呢?
第二天,实习生又没活干,裁吗?要是后天有活呢…
所以就要设一个最大时间,超过这个时间之后就裁掉

3.1.3 工作队列(阻塞队列)

image.png|454

  • 线程池的工作过程是典型的“生产者消费者模型”
  • 程序员工作的时候,通过形如“submit”这样的方法,把要执行的任务,设定到线程池里
  • 线程池内部的工作线程,负责执行这些任务
  • 此处就有一个阻塞队列

此处的队列可以让我们自行指定

  • 队列的容量—capcity
  • 队列的类型—基于链表?数组?优先级?…

通过 Runnable 给线程设定要执行的任务

  • Runnable 接口本身的含义就是一段可移植性的任务
  • Runnable 是基于任务的抽象表示

3.1.4 线程工厂

image.png|403

工厂设计模式

“工厂”指的是“工厂设计模式”,也是一种常见的设计模式

  • 是一种在创建类的实例时使用的设计模式
  • 由于构造方法有“坑”,通过这个设计模式来填坑

构造方法是一种特殊的方法

  • 必须和类名一样
  • 多个版本的构造方法必须构成“重载”(overload)实现

构造方法的局限性:

比如需要描述一个点(Point),有两种描述方法:

  1. 通过横纵坐标进行描述
    • 参数为 ( d o u b l e    x , d o u b l e    y ) (double\; x, double\; y) (doublex,doubley)
class Point{
	public Point(double x, double y) {}
}
  1. 通过极坐标进行描述(三角函数)
    • 参数为 ( d o u b l e    r , d o u b l e    a ) (double\; r, double\; a) (doubler,doublea)
    • x = r ∗ s i n α , y = r ∗ c o s α x=r*sinα,y=r*cosα x=rsinαy=rcosα
class Point {
	public Point(double r, double a){}
}
  • 但此时两个构造方法的参数个数和类型都是一样的,所以无法构成重载

因此,使用构造方法创建实例,就会存在上述局限性
为了解决上述问题,就引入了“工厂设计模式

  • 通过“普通方法”(通常是静态方法)完成对象构造和初始化的操作
class Point{
	public static Point makePointByXY(double x, double y) {
		Point p;
		p.setX(x);
		p.setY(y);
		return p;
	}
	
	public static Point makePointByRA(double r, double a) {
		Point p;
		p.setR(r);
		p.setA(a);
		return p;
	}
}
  • 这就是最简单的工厂设计模式的写法
  • 此处用来创建对象的 static 方法就称为“工厂方法”
  • 有的时候,工厂方法也会放到单独的类里实现,用来放工厂方法的类,称为“工厂类”
//工厂类
class PointFactory{
	public static Point makePointByXY(double x, double y) {
		Point p;
		p.setX(x);
		p.setY(y);
		return p;
	}
	
	public static Point makePointByRA(double r, double a) {
		Point p;
		p.setR(r);
		p.setA(a);
		return p;
	}
}

image.png|403

  • ThreadFactory 就是 Thread 类的工厂类,通过这个类,完成 Thread 实例创建和初始化操作
  • 此处的 ThreadFactory 可以针对线程池里的线程,进行批量的设置属性
  • 此处一般都不会进行调整,就是用标准库提供的默认值即可

3.1.5 拒绝策略

image.png

  • 最重要、最复杂的参数

如果线程池的任务队列满了,但还是要继续给这个队列添加任务,怎么办呢?

比如:你去向女神表白

  • 女神对你说:你是个好人(不见得是坏事),这样你可以趁早死心,趁早开始新的生活
  • 但如果女神只笑笑,也不说话,也不正面回答你,也不对你热情,也不对你冷淡,这才麻烦(很可能你就已经成为了备胎,你可能就在女神这里阻塞住)
    对于这两种情况,第一种肯定是更好,毕竟长痛不如短痛,伸头一刀缩头一刀,趁早让人死心
  • 所以,当队列满了,不要阻塞,而是要明确地拒绝
  • Java 标准库给出了四种不同的拒绝策略
拒绝策略类型说明
ThreadPoolExecutor. AbortPolicy默认拒绝策略,添加任务的时候,直接抛出异常
ThreadPoolExecutor.CallerRunsPolicy拒绝执行,由调用 submit 的线程负责执行
ThreadPoolExecutor.DiscardOldestPolicy把任务队列中最老的任务踢掉,然后执行新增加的任务
ThreadPoolExecutor.DiscardPolicy把任务队列中,最新的任务踢掉
四种拒绝策略

情景介绍:
这周你行程安排满了,你欲哭无泪,结果,你们导员突然找到你,让你和她一起参加一个什么比赛,在这时:

  1. AbortPolicy,添加任务的时候,直接抛出异常
    你心里最后一跟稻草被压到了:“本来就事多,还要我做牛马,我受不了了啊“,悲伤之下,你进了医院,你们导员也就理所应当的取消了这次比赛,并且你这周的满课也上不了了

  2. CallerRunsPolicy,拒绝执行,由调用 submit 的线程负责执行
    你耐心给导员说:“导员,我周事实在太多了,完全抽不开身”,于是,你们善解人意的导员就理解了,他就自己一个人去参加了那个比赛

  3. DiscardOldestPolicy,把任务队列中最老的任务踢掉,然后执行新增加的任务
    你不想拒绝导员,你看了下行程表,划去了最后一个任务,将所有的行程往后移,然后你跟着导员一起去参加比赛去了

  4. DiscardPolicy,把任务队列中,最新的任务踢掉
    你给导员说去不了之后,导员说:“那我也不去了”,于是这个比赛就被踢掉了

3.2 线程池的使用

ThreadPoolExecutor 功能很强大,使用很麻烦
标准库对这个类进一步封装了一下,Executors 提供了一些工厂方法,可以更方便构造出线程池
image.png

  • newCachedThreadPool 设置了非常大的最大线程数,可以对线程池进行不停地扩容
  • newFixedThreadPool 把核心线程数和最大线程数设置成了一样的值,固定了数量,不会自动扩容
public class Demo2 {  
    public static void main(String[] args) {  
        ExecutorService service = Executors.newFixedThreadPool(4);  
        for (int i = 0; i < 100; i++) {  
            service.submit(() -> {  
                Thread current = Thread.currentThread(); 
                //这里的i报错了,因为出现了“变量捕获” 
                System.out.println("hello thread" + i + "," + current.getName());  
            });        
        }    
    }
}
  • submit 也可以使用 lambda 表达式
  • 如果想 i 不报错,就需要 i 是被 final 修饰的或者是没有修改的(发生了 [[03 多线程-线程的核心操作#^191375|变量捕获]])
  • 我们就可以在 lambda 外面重新定义一个变量 id,每次进入循环就创建一个等于 i 的变量,之后之后也不会修改
public class Demo2 {  
    public static void main(String[] args) {  
        ExecutorService service = Executors.newFixedThreadPool(4);  
        for (int i = 0; i < 100; i++) {  
        int id = i;
            service.submit(() -> {  
                Thread current = Thread.currentThread(); 
                //这里的i报错了,因为出现了“变量捕获” 
                System.out.println("hello thread" + id + "," + current.getName());  
            });        
        }    
    }
}
  • 虽然代码的 100 个任务都执行完毕了,但是整个进程并没有结束,因为此处线程池创建出来的线程都是“前台线程”,虽然 main 线程结束了,但是这些线程池里的前台线程仍然存在
  • 若想解决
    1. 将每次创建出来的线程设为“后台进程”
    2. 使用 shutdown 操作,把线程池里面所有的线程都终止掉
public class Demo2 {  
    public static void main(String[] args) throws InterruptedException {  
        ExecutorService service = Executors.newFixedThreadPool(4);  
        for (int i = 0; i < 100; i++) {  
            int id = i;  
            service.submit(() -> {  
                Thread current = Thread.currentThread();  
                System.out.println("hello thread" + id + "," + current.getName());  
            });        
        }        
        Thread.sleep(2000);  
        service.shutdown();  
        System.out.println("程序退出");  
    }
}
  • 使用 sleep 是为了保证所有线程都执行完了

使用线程池的时候,需要指定线程个数,那该如何指定呢?该指定多少呢?

实际开发中建议的做法,是通过实验的方式,找到一个合适的线程数的个数的值

  • 给线程池设置不同的线程数,分别进行性能测试,关注响应时间/消耗的资源指标,挑选一个比较合适的数值

  • 一台主机上,并不是只有一个程序

  • 你写的这个程序,也不是 100%的每个线程都跑满 CPU,线程工作过程中,可能会涉及到一些 IO 操作/阻塞操作主动放弃 CPU

    • 如果线程代码里都是算术运算,确实能跑满 CPU
    • 如果是包含了 sleep,wait,加锁,打印,网络通信,读写硬盘… 都会使线程主动放弃 CPU 一会(给其他线程提供更多资源)

4. 线程池的模拟实现

固定线程数目的线程池


import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.BlockingQueue;  
  
class MyThreadPool {  
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);  
  
    //构造方法,n为创建的线程个数  
    public MyThreadPool(int n) {  
        //先创建 n 个线程  
        for (int i = 0; i < n; i++) {  
            Thread t = new Thread(() -> {  
                //循环的从队列中取出任务  
                while (true) {  
                    Runnable runnable = null;  
                    try {  
                        runnable = queue.take();  
                    } catch (InterruptedException e) {  
                        throw new RuntimeException(e);  
                    }                    
                    runnable.run();  
                }            
            });            
            t.start();  
        }    
    }  
    
    //添加任务  
    public void submit(Runnable runnable) {  
        try {  
            queue.put(runnable);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }    
    }
}  
  
public class Demo3 {  
    public static void main(String[] args) {  
        MyThreadPool pool = new MyThreadPool(4);  
        for (int i = 0; i < 1000; i++) {  
            int id = i;  
            pool.submit(() -> {  
                System.out.println("执行任务" + id + "," + Thread.currentThread().getName()); 
            });        
        }    
    }
}

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

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

相关文章

极投影ax.contourf

我想用极投影画个类似下面这样的效果图&#xff0c;首先底图是一个这样的&#xff0c;然后再有需要的地方做标记&#xff0c;比如斜线和渔网状的东西。 但是我怎么尝试都是得到了一个下面这样的图这样肯定有问题 我调整为画轮廓线之后变成下面这样的图&#xff0c;我猜想应该是…

Python酷库之旅-第三方库Pandas(073)

目录 一、用法精讲 296、pandas.Series.dt.as_unit方法 296-1、语法 296-2、参数 296-3、功能 296-4、返回值 296-5、说明 296-6、用法 296-6-1、数据准备 296-6-2、代码示例 296-6-3、结果输出 297、pandas.Series.dt.days属性 297-1、语法 297-2、参数 297-3、…

Ansys Mechanical|解决温度场-电场耦合问题

一.多物理场耦合分析的必要性 你是否曾经碰到过你的模型受到两个或者更多物理因素影响的情况吗&#xff1f;这些物理因素不仅会相互影响&#xff0c;还会影响计算结果。 这时多物理场分析就有用了。在解决多物理场影响的工程问题时&#xff0c;多物理场耦合分析是必要的。 举…

IO进程----标准IO

目录 IO进程 标准IO 1. 概念&#xff1a; 2. 特点&#xff1a; 3. 缓存区 3.1. 行缓存&#xff1a;和终端操作相关 刷新缓存的条件&#xff1a; 1) 程序正常退出 2) \n刷新 3) 缓存区满刷新 4) 强制刷新 fflush 3.2. 全缓存&#xff1a;和文件操作相关 3.…

【C++】模版详解

1、概念 C模版分两类&#xff1a;函数模版和类模版 1&#xff09;函数模板的格式 template <class 形参名&#xff0c;class 形参名&#xff0c;......> 返回类型 函数名(参数列表) {函数体 }例如&#xff1a; template <class T> void swap(T& a, T& b…

android13 关闭selinux 临时关闭或者永久关闭

总纲 android13 rom 开发总纲说明 目录 1.前言 2.情况分析 2.1 临时关闭 2.2 永久关闭 3.修改方法 3.1 临时修改 3.2 永久关闭 4.编译测试 5.彩蛋 1.前言 在Android操作系统中,SELinux(Security-Enhanced Linux)是一种安全模块,用于提供强制访问控制(MAC)安全…

为什么不用postman做自动化

面试的时候被问到&#xff1a;为什么不用postman做自动化 打开postman&#xff0c;看到用例集管理、API 管理、环境管理这三个功能&#xff0c;用户体验感算得上品牌等级了 为什么不用呢&#xff0c;文心一言给了一些答案 不适合大规模自动化测试&#xff1a;Postman 主要是为…

AI大模型排行榜(gpt-4o-2024-08-06)

https://github.com/yuchenlin/ZeroEval/blob/main/result_dirs/mmlu-redux.summary.md 人工智能学习网站 https://chat.xutongbao.top

学习日志8.8--防火墙精细化策略管控

本次实验的拓扑结构&#xff0c;用PC2去模拟和外部网络连接的Internet。 在trust和untrust区域上&#xff0c;希望将防火墙安全策略的默认动作修改为deny&#xff0c;然后精细化控制流量的访问&#xff0c;从trust到untrust控制只允许192.168.1.0的网络通过&#xff0c;从untr…

Spring Boot 3.x Filter实战:记录请求日志

上一篇&#xff1a;Spring Boot 3.x Web单元测试最佳实践 前面我们在《Spring Boot 3.x Rest API最佳实践之统一响应结构》中学习响应的统一拦截处理&#xff0c;顺带完成了响应结果的记录&#xff1b;而对于请求内容咱们也必须进行日志记录&#xff0c;以确保排查问题时有据可…

又一苹果经典产品宣布停产,老用户满满的回忆

苹果公司的SuperDrive&#xff0c;作为一项曾经引领潮流的外置光驱技术&#xff0c;自2008年伴随着革命性的MacBook Air轻盈登场以来&#xff0c;便成为了苹果电脑产品线中一道亮丽的风景线&#xff0c;象征着科技与美学的完美结合。 这款光驱以其超薄的设计、高效的读写速度以…

leetcode787. K 站中转内最便宜的航班——优先队列优化的Dijkstra算法+剪枝

题目 leetcode787. K 站中转内最便宜的航班 题目分析 给定一个城市图&#xff0c;每个城市通过航班与其他城市相连。每个航班都有一个起点、终点和价格。你需要找到从起点城市 src 到终点城市 dst 的最便宜路径&#xff0c;但这条路径最多只能经过 k 个中转站。你需要返回这…

构建智能生态,视频监控/安防监控EasyCVR视频汇聚流媒体技术在智能分析领域的应用

随着5G、AI、物联网&#xff08;IoT&#xff09;、云计算等技术的快速发展&#xff0c;万物互联的时代已经到来&#xff0c;全新的行业生态AIoT正在引领一场深刻的变革。在这场变革中&#xff0c;EasyCVR视频流媒体技术以其强大的视频处理、汇聚与融合能力&#xff0c;在智能分…

2024年计算机类学术会议有哪些

随着科技的飞速发展&#xff0c;计算机科学与技术领域正以前所未有的速度进步&#xff0c;各类学术会议成为了交流最新研究成果、探讨前沿技术趋势的重要平台。2024年&#xff0c;全球范围内将举办多场计算机类学术会议&#xff0c;这些会议不仅汇聚了顶尖的专家学者&#xff0…

创客匠人老蒋:你缺的不是客户,缺的是单个客户的营销和变现能力

老蒋创客圈第58期对话标杆直播连麦&#xff0c;我们邀请到【拾才易人】平台创始人侯邦辉老师。侯老师与创客匠人合作3年之久&#xff0c;实现了线上线下高转化&#xff0c;并实现家庭教育、心理疗愈、国学传承与营销运营一站式学习平台。 上篇文章&#xff0c;我们主要梳理了连…

Gemma Scope 帮助理解 AI 模型的内部工作原理

Gemma Scope 工具如何帮助理解 AI 模型&#xff08;具体来说是 Gemma 模型&#xff09;的内部工作原理&#xff0c;通过观察模型的“特征”来理解 AI 是如何“思考”的 1 特征&#xff08;features&#xff09; 了解AI模型在思考什么。Gemma Scope 将 Gemma 模型的大脑分解成…

延时队列与redis and rabbitmq

延时队列是什么 延时队列&#xff08;Delay Queue&#xff09;是一种特殊的消息队列&#xff0c;它允许你在添加消息时设置一个延时时间&#xff0c;消息只有在延时时间到达后才能被消费。这种机制在分布式系统中非常有用&#xff0c;常用于处理需要在指定时间后执行的任务&am…

光耦合器知识概述

光耦合器&#xff0c;又称光电耦合器&#xff0c;是一种通过光信号来实现电信号隔离的电子元件。它在确保电路安全和信号完整性方面起着关键作用&#xff0c;广泛应用于电源管理、工业自动化、消费电子等领域。本文将深入探讨光耦合器的工作原理、技术参数、应用场景、选型指南…

【JAVA入门】Day19 - BigInteger 和 BigDecimal

【JAVA入门】Day19 - BigInteger 和 BigDecimal 文章目录 【JAVA入门】Day19 - BigInteger 和 BigDecimal一、BigInteger1.1 BigInteger 构造方法1.2 BigInteger 内部常见方法 二、BigDecimal2.1 BigDecimal 的作用2.2 BigDecimal 对象的获取2.3 BigDecimal 中常见的成员方法2.…

自动驾驶计算芯片企业“流血”上市,小米、腾讯等曾投7亿美元

"自动驾驶市场何时迎来爆发&#xff1f;" 作者 | 魏 强 编辑 | 卢旭成 今天&#xff0c;自动驾驶计算芯片企业Black Sesame International HoldingLimited(黑芝麻智能)在港交所上市&#xff0c;发行价28港元&#xff0c;募资总额10.36亿港元。 黑芝麻智能号称国…