多线程典型例子(4)——线程池

news2024/11/27 3:55:39

文章目录

  • 一、线程池的基本情况
  • 1.1、使用线程池的必要性
  • 1.2、线程池为什么比直接在系统中创建线程更高效?
    • 1.2.1、纯内核态操作
    • 1.2.2、纯用户态操作
  • 1.3、那为什么用户态操作比内核态操作更高效?
  • 二、如何在Java中使用线程池
    • 2.1、ExecutorService
    • 2.1、ThreadPoolExecutor[重点]
      • 2.1.1、谈谈Java标准库里的 线程池 构造方法的参数和含义?[经典面试题]
  • 三、自我实现一个线程池
    • 3.1、输入线程数目时如何界定?
      • 3.1.1、CPU密集型
      • 3.1.2、IO密集型

一、线程池的基本情况

1.1、使用线程池的必要性

虽然线程是 “轻量级进程” ,但当我们频繁的创建/销毁 线程时,其所产生的成本不可忽视。因此可以使用 线程池,提前创建好一些线程放在线程池中,后续需要使用线程时,直接从线程池中随取随用即可,当线程不再使用时,就放回池子里。那么此时效率就会大幅度提升。

1.2、线程池为什么比直接在系统中创建线程更高效?

1.2.1、纯内核态操作

但是为什么从线程池中取线程,就比在操作系统里创建线程,更高效呢?
1、如果是从系统处来创建线程,就需要调用系统API,进一步的由系统内核来完成创建线程的操作,譬如说:创建一个PCB,然后再将PCB加入到链表中…(纯内核态操作)

1.2.2、纯用户态操作

2、如果是直接从线程池处获取线程,上述内核态中进行的操作,都已经提前完成了,从线程池中取线程的过程,纯粹是由用户代码完成。(纯用户态)

1.3、那为什么用户态操作比内核态操作更高效?

举个例子:
比如说我们需要去银行办业务,柜台是非工作人员无法进入,此时柜台处就相当于内核态,大众可以在银行大厅随意溜达,此时银行大厅就相当于是用户态。假如A要去柜台让工作人员帮他解冻银行卡,工作人员需要A提供一下身份证复印件来办理业务,但是A没有带复印件,此时工作人员就给出了2个建议:1、工作人员帮他复印。2、银行大厅有复印机,A可以自行进行复印。

如果A让工作人员帮其进行复印,有可能工作人员在帮A复印时,被领导交代了别的比较急的业务(因为内核态是给所有进程提供服务的,工作人员也一样,他也会为很多用户提供服务 ),此时工作人员就会放下手里的复印的工作,先去完成领导交代的业务,过了一会后才继续帮A进行复印。那么此时A就不知道自己什么时候才能拿到复印件,到底是多久,这是不可控的,取决于工作人员的效率,A无法控制。

如果A选择自行到银行大厅进行复印,此时什么时候能复印完,是可控的。

那此时就是A自己复印,比让工作人员帮忙复印,效率会更高更快。

二、如何在Java中使用线程池

2.1、ExecutorService

Java标准库中,提供了现成的线程池类——ExecutorService

ExecutorService 类中提供4个用来 创建线程池对象的过程 的工厂方法:
1、Executors.newFixedThreadPool(int x)
      创建一个固定线程数量的线程池
2、Executors.newCachedThreadPool()
      创建一个线程数目动态变化的线程池:譬如说当首次使用线程池时,线程池可能会没有线程,此时因为要执行任务,就会创建出一个线程,当一个线程不够用时,会动态的创建出其他线程,创建出的线程就不用回收了,以便下次使用。
3、Executors.newSingleThreadPool()
      创建一个只有单个线程的线程池
4、Executors.newScheduleThreadPool()
      包含单个线程的线程池,类似于定时器的效果。添加一些任务,任务都在后续某个时刻再执行,被执行的时候不是只有一个扫描线程来执行任务,可能是由多个线程共同执行所有任务。

Executors 称为 “工厂类”。

public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(4);
        for(int i = 0;i<1000;i++){
	        /**
	        * 线程池对象创建好了之后,就可以使用 submit() 把任务添加到线程池中
	        *  
	        * 但由于线程池不止要执行一个任务,因此光一个 submit() 还远远不够,
	        * 所以可以在  submit() 外加一个 for循环,1000 表示 有 1000 个任务,
	        * 线程池中的4个线程需要共同执行/分担这1000个任务
	        **/
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Runnable 里的 run() 里,就是我们具体要执行的任务!");
                }
            });
        }
    }

运行结果:(1000个任务,所以执行了1000下,打印了1000个输出)在这里插入图片描述

2.1、ThreadPoolExecutor[重点]

Java标准库除了提供上述的线程池标准类外,还提供了一个接口更丰富的线程池类—— ThreadPoolExecutor

ThreadPoolExecutor类有4个构造方法:
在这里插入图片描述

2.1.1、谈谈Java标准库里的 线程池 构造方法的参数和含义?[经典面试题]

由于该线程池类 ThreadPoolExecutor 中的线程数并不是一成不变的,而是根据任务的情况动态变化(自适应),如果任务多,该线程池中的线程数就多一些(创建出来),任务少,该线程池中的线程数就少一些(多余的就销毁),但是此处的动态变化也并不是没有限制,因此ThreadPoolExecutor类的构造方法提供了几个不同含义的参数,来对线程池的动态变化产生一定限制。(如:corePoolSize、maximumPoolSize)

1、介绍构造方法中的参数:
      int corePoolSize
核心线程数(线程池里最少也得有这些数量的线程,哪怕线程池里一点任务也没有)

2、int maximumPoolSize
      最大线程数(最多不能超过这些线程。哪怕线程池由于需要执行很多任务忙疯了,动态变化的线程数也不能比这个数目更多了)

举个例子理解上述两个参数:
在这里插入图片描述
这样设定之后,公司繁忙时,就可以在含有正式员工的情况下,招收一些实习生帮忙;在公司不忙的时候,可以将多余的实习生裁掉。

对于线程池来说也是一样,这样的设定,既能保证繁忙的时候高效的处理任务,又能在空闲的时候不会浪费资源。

3、long keepAliveTime
      允许摸鱼的最大时间数(当公司业务不繁忙时,实习生就都空闲下来了,那么公司会立即裁掉实习生吗??不是,是当实习生线程空闲超过指定的时间阈值后,就会被销毁。)

4、TimeUnit unit
      时间单位(譬如 ms、s…)

5、 BlockingQueue workQueue
      线程池内部会有很多任务要线程去执行,这些任务,可以使用阻塞队列来管理起来

6、ThreadFactory threadFactory
      这是一个工厂类,主要用于创建线程的

7、RejectedExecutionHandler handler [考察的重点参数]
      。拒绝方式/拒绝策略。 ThreadPoolExecutor 的线程池,有一个阻塞队列,当阻塞队列中的任务满了之后,继续添加任务,该如何应对??阻塞?不太合适。因此就有了4种拒绝策略:
1)、ThreadPoolExecutor.AbortPolicy
      直接抛出异常,线程池直接不干活了
2)、ThreadPoolExcutor.CallerRunsPolicy
       谁是添加这个新任务的线程,那么这个任务就由谁来执行
3)、ThreadPoolExecutor.DiscardOldestPolicy
      丢弃前边最早的任务,执行新的任务(晚来的任务)。
4)、ThreadPoolExecutor.DiscardPolicy
      直接把最新的任务丢弃了

三、自我实现一个线程池

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 自我实现一个固定数量的线程池
 */
class MyExecutor {
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    /**
     * 线程数
     *
     * @param n
     */
    public MyExecutor(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();
        }
    }
}

public class testMyExecutor {
    public static void main(String[] args) throws InterruptedException {
        MyExecutor myExecutor = new MyExecutor(4);
        for (int i = 0; i < 1000; i++) {
            myExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"hello");
                }
            });
        }
    }
}

3.1、输入线程数目时如何界定?

那么创建线程池时,使用创建固定线程数目的线程池的方法时,这个线程池的线程数目究竟是该填几呢?4?8?12?

都不准确。没有什么固定数量!不同项目里,线程要做的工作都是不一样的。有的线程进行的工作是:“CPU密集型”(该线程做的工作大多/全是运算),有的线程的工作是:“IO密集型”(该线程进行的工作可能是:读写文件、等待用户进行输入、网络通信…)。

3.1.1、CPU密集型

"CPU密集型"大部分工作都是要在CPU上执行的,CPU得给线程安排cpu核心去完成工作才行。因此在这个情况下,如果CPU是N个核心,当线程数量也是N时,理想情况下,即每个核心上一个线程,如果有更多线程数,线程只能进行排队等待CPU核心,不会有什么新的进展(譬如说效率提升之类的,不会),甚至于可能物极必反,太多线程数导致线程调度开销变大,影响效率。

3.1.2、IO密集型

“IO密集型”大部分的线程需要进行大量的等待时间,(譬如等待用户进行输入),等的过程中,并没有使用CPU,此时就算线程数就算多一些,也不会给CPU造成太大负担。比如CPU为16个核心,写个32个线程,由于这些线程在进行着“IO密集型”工作,这里大部分的线程都在等,并不消耗CPU,反而CPU的被占用情况还比较低。

但上述描述的是一种 “理想状态”,在实际开发中,既会有部分线程进行 “CPU密集型”工作,又会有部分线程进行“IO密集型”工作。

因此最好的做法就是通过实验(对程序进行性能测试,测试时尝试不同线程数,找到性能和系统资源开销比较均衡的数值)的方式,来找到线程池中合适的线程数目。

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

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

相关文章

常见JavaWeb混合Vue.js课设中的要点

在校期间我们要做很多课设&#xff0c;实际上&#xff0c;学校教的大概率不足以让多数学生独立做出系统。在网上随便一搜&#xff0c;大抵都是千篇一律的“XXXX”管理系统。这些项目出于方便&#xff0c;往往采用vue作为前端框架而不用原生的JavaScript。 vue的本质要点是避免原…

FPGA HDMI Sensor无线航模摄像头

FPGA方案&#xff0c;接收摄像头sensor 图像数据后&#xff0c;通过HDMI输出到后端 客户应用&#xff1a;无线航模摄像头 主要特性&#xff1a; 1.支持2K以下任意分辨率格式 2.支持多种型号sensor 3.支持自适应摄像头配置&#xff0c;并补齐输出时序 4.可定制功能&#xff…

一文详解|影响成长的关键思考(二)

之前写过一篇《一文详解&#xff5c;影响成长的关键思考》&#xff0c;里面对自己工作前几年的心法进行了总结&#xff0c;并分享了出来。现在又工作了一段时间后&#xff0c;有了一些新的体会&#xff0c;想进一步分析一下&#xff0c;于是便有了此文。的确&#xff0c;思考也…

2024服贸会,参展企业媒体宣传报道攻略

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 2024年中国国际服务贸易交易会&#xff08;简称“服贸会”&#xff09;是一个重要的国际贸易平台&#xff0c;对于参展企业来说&#xff0c;有效的媒体宣传报道对于提升品牌知名度、扩大…

docker学习笔记(五):harbor仓库搭建与简单应用

harbor私有仓库 简介 Docker容器应用的开发和运行离不开可靠的镜像管理&#xff0c;虽然Docker官方也提供了公共的镜像仓库&#xff0c;但是从安全和效率等方面考虑&#xff0c;部署私有环境内的Registry也是非常必要的。Harbor是由VMware公司开源的企业级的Docker Registry管…

【快捷部署】022_ZooKeeper(3.5.8)

&#x1f4e3;【快捷部署系列】022期信息 编号选型版本操作系统部署形式部署模式复检时间022ZooKeeper3.5.8Ubuntu 20.04tar包单机2024-05-07 一、快捷部署 #!/bin/bash ################################################################################# # 作者&#xff…

晶片的厚度会影响晶振的频率吗?

晶振&#xff0c;是一种能够产生稳定频率的电子元件&#xff0c;广泛应用于各种电子设备中。晶振的频率参数&#xff0c;即其振荡产生的频率大小&#xff0c;是晶振性能的重要指标之一。石英晶体的切割方式显得至关重要。不同的切割方式&#xff0c;如AT-cut、CT-cut、SC-cut等…

实力再获认可!WeTrade荣获“最佳交易流动性”大奖

WeTrade在泰国举行的颁奖典礼上荣获“最佳交易流动性”奖项。 颁奖典礼于2024年4月28日在曼谷 Grande Centre Point Surawong 隆重举行&#xff0c;与FastBull 2024交易影响力颁奖典礼同期举办。FastBull是一家全球领先的金融资讯平台&#xff0c;覆盖全球多市场金融服务&…

推荐4个可用的github国内镜像

Github是全球最大的代码托管云平台&#xff0c;超过1亿用户在平台上分享代码及数据&#xff0c;深受生物信息学软件开发者的喜爱&#xff0c;并且现在发表文章&#xff0c;若涉及到代码&#xff0c;编辑还要求我们把代码及数据存放在github上&#xff0c;以便检查数据的真实性和…

风电齿轮箱轴承为风电齿轮箱核心部件 滑动轴承为其主要类型

风电齿轮箱轴承为风电齿轮箱核心部件 滑动轴承为其主要类型 风电齿轮箱轴承全称为风力发电机组齿轮箱轴承&#xff0c;为风电齿轮箱核心部件&#xff0c;起到减少摩擦损失、支撑齿轮等作用。风电齿轮箱轴承具备耐腐蚀、可靠性高、体积小、使用寿命长等优势&#xff0c;在大型风…

SparkStructuredStreaming状态编程

spark官网关于spark有状态编程介绍比较少&#xff0c;本文是一篇个人理解关于spark状态编程。 官网关于状态编程代码例子: spark/examples/src/main/scala/org/apache/spark/examples/sql/streaming/StructuredComplexSessionization.scala at v3.5.0 apache/spark (github…

华为OD机试 - 手机App防沉迷系统(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

初识Java的main方法

创建一个Java文件 main方法以及用cmd运行程序的过程 面试题JDK\JRE\JVM之间的关系 注意事项 解析String[ ] args 我们想知道String[ ] args里面到底是什么&#xff0c;我们可以用for循环遍历这个数组 Java代码结构 编写Java程序时可能会遇见的错误 注释 注释是为了让代码更…

在做题中学习(56):二维前缀和模板

【模板】二维前缀和_牛客题霸_牛客网 (nowcoder.com) 理解题意&#xff1a; 要求的是(x1,y1) - (x2,y2)这段区间的和。 解法&#xff1a;二维前缀和 1. 和一维前缀和一样&#xff0c;需要有一个同等规模的dp数组&#xff0c;用来保存一段连续区域的和。 在二维dp中&#xff0…

探案录 | KingbaseES+SqlSugar为医疗用户排忧解难

在2024年的初春&#xff0c;某大型三甲医院的CT预约系统上线测试&#xff0c;如同新芽破土&#xff0c;充满了希望与活力。然而&#xff0c;仅仅两天后&#xff0c;一个技术难题如同迷雾中的幽灵&#xff0c;悄然出现&#xff1a;The connection pool has been exhausted…… 福…

图形网络的自适应扩散 笔记

1 Title Adaptive Diffusion in Graph Neural Networks&#xff08;Jialin Zhao、Yuxiao Dong、Ming Ding、Evgeny Kharlamov、Jie Tang&#xff09;【NIPS 2021】 2 Conclusion The neighborhood size in GDC is manually tuned for each graph by conductin…

《看漫画学C++》背后的故事5:超人C++

《看漫画学C》是一本以漫画形式介绍C编程语言的书籍。在第1章中&#xff0c;作者大羽老师通过超人的形象来比喻C是C语言的增强版&#xff0c;这种比喻生动形象&#xff0c;易于理解。在漫画中&#xff0c;超人通常拥有超越常人的能力&#xff0c;这可以类比C相对于C语言增加的高…

Vue+OpenLayers7入门到实战:OpenLayers解析通过fetch请求的GeoJson格式数据,并叠加要素文字标注,以行政区划边界为例

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7入门到实战 前言 本章介绍如何使用OpenLayers7在地图上通过fetch请求geojson数据,然后通过OpenLayers解析为Feature要素叠加到图层上,并且通过动态设置标注方式显示要素属性为文字标注。 本章还是以行政区划边界为例,这个…

何为基差?股指期货的升水和贴水又怎么理解?

基差是一个金融术语&#xff0c;它指的是现货价格和期货价格之间的差额。在股指期货市场中&#xff0c;现货就是指实际的股票指数&#xff0c;而期货则是基于这个指数未来某个时间点的价格预期。基差可以是正的或负的&#xff0c;具体取决于期货价格是高于还是低于现货价格。 1…

vue3 - 图灵

目录 vue3简介整体上认识vue3项目创建Vue3工程使用官方脚手架创建Vue工程[推荐] 主要⼯程结构 数据双向绑定vue2语法的双向绑定简单表单双向绑定复杂表单双向绑定 CompositionAPI替代OptionsAPICompositionAPI简单不带双向绑定写法CompositionAPI简单带双向绑定写法setup简写⽅…