线程池概念、线程池的不同创建方式、线程池的拒绝策略

文章目录

  • 💐线程池概念以及什么是工厂模式
  • 💐标准库中的线程池
  • 💐什么是工厂模式?
  • 💐ThreadPoolExecutor
  • 💐模拟实现线程池

💐线程池概念以及什么是工厂模式

线程的诞生是因为,频繁的创建进程太重量了(开销较大),所以引入了线程,但是呢,对于线程来讲,如果更加频繁的创建和销毁,那么开销也会慢慢的变大,所以,又引入了两种经典的方法来进一步提高:

1.协程:又称为轻量级线程,线程比较轻量是因为线程省略了分配资源的环节,而协程它在着基础上又省略了操作系统调度执行的环节,由程序员自己调度;在Java中呢,主要使用线程池,所以对于协程只是简单提一下;

2.线程池

举一个例子:

假如我是一个很漂亮的妹子,又有许多的男生正在追我,然后我就选择了一个男生A做我男朋友,但是呢,经过一段时间之后,我就腻了,就想要和男生B谈恋爱,所以,我就和男生A提出了分手,然后和男生B培养感情,等到有了感情基础,等有了感情基础后就拿下男生B,但是,过来一段时间后,我又想和男生C谈恋爱,所以就接着重复上面的套路,先培养感情等等………

而对于上面这种换男朋友的方式,感觉效率太慢,所以,我就有了一种新的方式,在和男生A谈恋爱的同时,偷偷的和男生B、C、D等多个男生培养感情,等到我向和谁谈恋爱时,那不就是捅破一层窗户纸的事情么,就可以挑选一个直接谈恋爱,这样的效率不久高了很多么,所以,对于偷偷的和我培养感情的这群男生也就可以称为“备胎池”;

而我们的线程池也是上面这种模式,在向池中添加任务时,直接从线程池中拿线程就可以了,就不比再创建了,直接拿过来使用即可,这样也就降低了线程创建的开销;所以线程池的就是先把线程创建好,放进池子里,等到后续想要使用时,直接从池子里取;

这里就会有一个问题:为啥从线程池里面取线程比创建线程效率高?

首先,创建新的线程这个动作,是内核态+用户态相互配合完成的;

而从线程池中取这个动作,是用户态操作完成的;

所以这里就涉及到了两个新名词,什么是用户态,什么是内核态

如果一段程序是在系统内核中完成的,此时就称为内核态

如果不是,则称为用户态;

而操作系统呢,是由内核+配套的应用程序组成的,创建线程,就需要调用系统API,进入到内核中,按照内核态的方式来完成一系列的动作;

但是,为什么内核态操作的效率比较低呢?请看下图

💐标准库中的线程池

在Java中,提供了一个类——Executors创建线程池;但是,线程池对象的创建并不是直接new出来的,而是通过一个方法的返回值,返回了一个线程池对象;

public class MyThreadPool {
    public static void main(String[] args) {
        //创建一个动态的线程池
        ExecutorService es = Executors.newCachedThreadPool();
        es.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
    }
}

创建线程池对象分为以下步骤:

1.使用Executors.newCachedThreadPool 创建出一个动态增长的线程池,为什么要用Executors.newCachedThreadPool的方式创建线程池,而不是直接new Executors?这里就涉及到了一个设计模式——工厂模式:

💐什么是工厂模式?

工厂模式:定义一个工厂类,通过调用工厂类中的不同方法来实现对象的实例化,从而创建出不同作用的对象;

举个例子,我们在创建对象时,会使用new关键字,通过构造方法来创建对象,但是使用构造方法创建对象会又很大的局限性,举个例子:假如我现在想要使用笛卡尔坐标来创建一个点对象,代码如下:

public class Point {
    //笛卡尔坐标系需要提供一个x,y坐标
    private int x;
    private int y;
    //通过笛卡尔坐标的方式创建一个对象
    public Point(int x, int y) {};
}

但是,我现在又想通过极坐标的方式创建点对象

public class Point {
    private int x;
    private int y;
    //通过笛卡尔坐标的方式创建一个对象
    public Point(int x, int y) {};
    //极坐标的方式就需要提供一个半径和角度
    public Point(int r, int a) {};
}

但是以上这种方式就会编译错误,因为,如果想要使用多种构造方法的方式创建对象的话,就需要将构造方法重载,而重载的条件是要保证参数列表的类型或者个数不同,所以以上代码是行不通过的,针对这种问题,就可以利用工厂模式解决

public class Point {
    private int x;
    private int y;
    
    public void setX(int x) {
        this.x = x;
    }
    public void setY(int y) {
        this.y = y;
    }
}

//创建一个点对象的工厂
class PointFactor{
    //通过笛卡尔坐标系创建对象
    public static Point newPointByXY(int x, int y) {
        Point p = new Point();
        //对Point中的属性进行初始化
        p.setX(x);
        p.setY(y); 
        return p;
    }
    //通过极坐标创建对象
    public static Point newPointByRA(int r, int a) {
        Point p = new Point();
        p.setX(r);
        p.setY(a);
        return p;
    }
}

//测试类
class Main{
    public static void main(String[] args) {
        //这样通过调用点工厂中不同方法,就可以根据不同的方式创建出对象
        Point point1 = PointFactor.newPointByXY(5,2);
        Point point2 = PointFactor.newPointByRA(10,20);
    }
}

回到这里的线池:

public class MyThreadPool {
    public static void main(String[] args) {
        //创建一个动态的线程池
        ExecutorService es = Executors.newCachedThreadPool();
        es.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
    }
}

在线程池中,也提供了几个比较重要的方法:

(重点)创建线程池也提供了几种不同的方式:

  • newCachedThreadPool() 创建线程数目动态增长的线程池

    这种方式创建的线程池,池子中的线程会根据你添加的任务的需要,自动创建线程出来,线程结束以后也不会立即销毁,而是会在池子中保留一段时间,以备后续再随时使用;

  • newFixedThreadPoll() 创建固定线程数的线程池

  • newSingleThreadPool() 创建只包含一个线程的线程池

  • newScheduleThreadPool() 类似于定时器,只不过不是一个扫描线程,而是多个扫描线程执行时间到的任务

方法的返回值类型是ExecutorService

通过ExecutorService定义的对象调用submit()方法注册一个任务到线程池中

    public static void main(String[] args) {
        //创建一个动态的线程池
        ExecutorService es1 = Executors.newCachedThreadPool();
        ExecutorService es2 = Executors.newFixedThreadPool(5);
        ExecutorService es3 = Executors.newSingleThreadExecutor();
        ExecutorService es4 = Executors.newScheduledThreadPool(6);//指定扫描线程的数量
        
    }

上述几个使用工厂方法创建的线程池,本质上都是对一个类进行了封装,这个类就是——ThreadPoolExecutor

这个类的功能非常丰富,提供了很多不同参数方法,上述的几个工厂方法呢,其实就是给这个ThreadPoolExecutor 类填写了不同的构造参数从而创建出了不同的线程池;

接下来就看一下ThreadPoolExecutor的使用方法👇

💐ThreadPoolExecutor

ThreadPoolExecutor的构造方法中提供了很多可选的参数,进一步的细化了线程池的设定,下面针对这些参数进行一个讲解:

在这里插入图片描述

上图就是ThreadPoolExecutor的所有构造方法,也可以看到,最后一个构造方法的参数最多,并且当中的参数也都包含了其他三个方法的参数,所以,这里针对最后一个方法参数进行讲解:

  • int corePoolSize :核心线程数

  • int maximumPoolSize :最大线程数目

    在一个线程池中,是有多个线程的,以上两个参数就指定了线程池中线程数目的范围,最少有corePoolSize个线程,最多不会超过maxMumPoolSize个线程

  • long keepAliveTime 和 TimeUnit unit :空余线程存活的时间以及时间的单位

    在创建线程时, 默认会先使用核心线程数,上面提到过,当任务执行结束后,线程不会立马销毁,而是会有一个保留的时间,一方面是为了如果后续再需要使用时,就不用再进行创建,另一方面是,当保留时间到了以后,进行销毁,也减少了资源消耗,后续使用时再进行创建即可

  • BlockingQueue workQueue :阻塞队列

    当使用submit向线程池中添加任务时,如果任务个数少于核心线程数,那么会创建新的线程去执行任务,如果任务个数超过了核心线程数,就会先添加到阻塞队列中,然后工作线程从队列中取出任务执行,如果任务不能排队等候,那么也会创建一个新的线程,前提是不会超过最大的线程数,需要注意的是,这里的队列不光可以是阻塞队列,还可以是其他的队列,例如如果需要使用优先级就可以设置为:PriorityBlockingQueue,如果任务数目变动不大就可以使用:ArrayBlockingQueue,如果任务数目变动较大就可以使用:LinkedBlockingQueue;

  • ThreadFactory threadFactory :线程工厂类

    这个类也是工厂模式的体现,由ThreadFactory这个工厂类来创建线程,使用工厂类创建线程,主要是对线程的属性进行设置,通过这个类对这些属性进行了封装,就不需要我们手动进行设置;

  • ThreadPoolExecutor.DiscardPolicy :拒绝策略

    一个线程池中的线程数量是有上限的,当线程数量达到上限后,如果还继续往线程池中添加任务,那么针对不同的拒绝策略就会出现不同的效果;

    (重点)任务策略分为:

    在这里插入图片描述

这四种拒绝策略使用了类来实现,想要使用哪种策略,直接创建出对象,将对象传过去即可;

下面针对这四种策略进行一个讲解:

ThreadPoolExecutor.AbortPolicy :如果队列已经满了,直接抛出异常

ThreadPoolExecutor.CallerRunsPolicy :新添加的任务由调用任务的线程执行

ThreadPoolExecutor.DiscardOldestPolicy :丢弃任务队列中最老的任务

ThreadPoolExecutor.DiscardPolicy :丢弃新添7加的任务

上面讲过,针对于线程池可以设置线程的数目,但是,这个数目设置成多少合适呢?

针对这个问题,网上有很多的答案,假设CPU的逻辑核心数是N,线程数目的设置就有多个答案,例如:

N个,N+1个,N+2个,2N个等等;

针对以上答案,没有一个是正确的,因为这需要根据项目代码进行设置,一个线程执行的代码主要分为两类:

  • CPU 密集型

    CPU 密集型,代码里面的主要逻辑都是在算数运算/逻辑判断

  • IO 密集型

    IO 密集型,代码里面的主要逻辑都是在进行IO操作

    如果代码是都是CPU密集型,这时设置的线程数目就不能超过N,如果代码都是IO密集型的,此时设置的线程数目就可以超过N,而在现实中,没有代码都是纯CPU密集型和纯IO密集型的,同时,我们也无法知道有多少代码是CPU密集,有多少代码是IO密集的;

    所以要想知道该设置多少线程数,正确的做法就是用实验的方式,尝试改变线程池中不同线程的数目,来观察出哪种数目更合适;

💐模拟实现线程池

这里来模拟一个简单的newFixedThreadPool()版本的线程池,步骤:

  • 创建一个MyThreadPool,描述一个线程池

  • 使用一个阻塞队列组织所有的任务

    public class MyThreadPool {
        //创建一个阻塞队列组织所有的任务
        private BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();
        public MyThreadPool(int n) {
            //创建n个线程
            for(int i = 0; i < n; i++) {
                Thread thread = new Thread(() -> {
                    try {
    
                        Runnable runnable = queue.take();
                        runnable.run();
    
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                });
                thread.start();
            }
        }
        public void submit(Runnable runnable) throws InterruptedException {
            queue.put(runnable);
        }
        public static void main(String[] args) throws InterruptedException {
            MyThreadPool myThreadPool = new MyThreadPool(4);
            for(int i = 0; i < 100; i++) {
                int n = i;
                myThreadPool.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(n);
                    }
                });
            }
        }
    }
    

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

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

相关文章

原Veritas(华睿泰)中国研发中心敏捷教练、项目集经理郑鹤琳受邀为第十三届中国PMO大会演讲嘉宾

全国PMO专业人士年度盛会 原Veritas&#xff08;华睿泰中国&#xff09;中国研发中心敏捷教练、项目集经理郑鹤琳女士受邀为PMO评论主办的2024第十三届中国PMO大会演讲嘉宾&#xff0c;演讲议题为“敏捷项目管理-知行合一”。大会将于6月29-30日在北京举办&#xff0c;敬请关注…

LabVIEW与数字孪生

LabVIEW与数字孪生技术在工业自动化、智慧城市、医疗设备和航空航天等领域应用广泛&#xff0c;具备实时数据监控、虚拟仿真和优化决策等特点。开发过程中需注意数据准确性、系统集成和网络安全问题&#xff0c;以确保数字孪生模型的可靠性和有效性。 经典应用&#xff1a;LabV…

数据挖掘常见算法(分类算法)

K&#xff0d;近邻算法&#xff08;KNN&#xff09; K-近邻分类法的基本思想&#xff1a;通过计算每个训练数据到待分类元组Zu的距离&#xff0c;取和待分类元组距离最近的K个训练数据&#xff0c;K个数据中哪个类别的训练数据占多数&#xff0c;则待分类元组Zu就属于哪个类别…

win10 修改远程桌面端口,在Win10上修改远程桌面端口的要怎么操作

在Windows 10上修改远程桌面端口是一个涉及系统配置的过程&#xff0c;这通常是为了增强安全性或满足特定网络环境的需要。 一、通过注册表编辑器修改远程桌面端口 1. 打开注册表编辑器&#xff1a; - 按下Win R组合键&#xff0c;打开“运行”对话框。 - 在“运行”对话框…

结构体 (一)

在我们C语言中&#xff0c;为我们提供了不同的内置类型&#xff0c;例如&#xff1a;char 、short 、int 、long 、float 、double 等等&#xff0c;但是呢&#xff0c;仅仅只有这些内置类型是远远不够的&#xff0c;当我们想要描述一名学生&#xff0c;一本书&#xff0c;一件…

Linux:目录和文件管理命令2

目录 一、Linux目录结构&#xff1a; 二、查看文件 2.1、cat 命令——显示并连接&#xff08;Concatenate&#xff09;文件的内容 2.2、more 和 less 命令——分页查看文件内容 2.3、head 和 tail 命令——查看文件开头或末尾的部分内容 三、统计和检索文件内容 3.1、wc…

不知大家信不信,竟有这么巧的事,我领导的老婆,竟然是我老婆的下属,我在想要不要利用下这层关系,改善下领导对我的态度,领导怕老婆

职场如战场&#xff0c;每个人都身不由己。每天上班&#xff0c;除了要面对堆积如山的工作&#xff0c;还要小心应对来自领导的“狂风暴雨”。最近&#xff0c;我无意间发现领导一个秘密&#xff0c;这个秘密让我对职场关系和人性都产生了新的思考。 故事要从那天晚上说起。我…

ARM相关理论知识

一、计算机的组成 1.输入设备&#xff1a;将数据与程序转换成计算机能够识别&#xff0c;存储&#xff0c;运算的形式&#xff0c;输送到计算机中。 2.输出设备&#xff1a;将计算机对程序和数据的运算结果输送到计算机外部设备 3.控制器&#xff1a;由程序技术器&#xff0…

《数字图像处理》实验报告一

一、实验任务与要求 1、用 matlab 编写空间域点处理操作处理给定的几幅图像&#xff0c;要求&#xff1a; 使用 imread 读取当前工作目录下的图像设计点处理操作并用代码实现处理用 imnshow 显示处理后的图像用 imwrite 保存处理后的图像 2、提交内容&#xff1a;m文件 实验…

【ChatBI】超轻量Python库Vanna快速上手,对接oneapi

oneapi 准备 首先确保你有oneapi &#xff0c;然后申请 kimi的api 需要去Moonshot AI - 开放平台 然后添加一个api key 然后打开oneapi的渠道界面&#xff0c;添加kimi。 然后点击 测试&#xff0c; 如果能生成响应时间&#xff0c;就是配置正确。 然后创建令牌 http:…

渗透测试基础(六) MS10-046漏洞攻击

1. 漏洞介绍 1.1 漏洞介绍 Microsoft Windows快捷方式LNK文件自动执行代码漏洞。Windows支持使用快捷方式或LNK文件。LNK文件是指向本地文件的引用,点击LNK文件与点击快捷方式所制定的目标具有相同效果。Windows没有正确的处理LNK文件,特制的LNK文件可能导致Windows自动执行…

微服务(服务治理)

服务远程调用时存在的问题 注册中心原理 服务治理中的三个角色分别是什么&#xff1f; 服务提供者&#xff1a;暴露服务接口&#xff0c;供其它服务调用服务消费者&#xff1a;调用其它服务提供的接口注册中心&#xff1a;记录并监控微服务各实例状态&#xff0c;推送服务变更信…

MIT6.s081 2021 Lab Utilities

Boot xv6 按照示例切换到 util 分支后&#xff0c;看到目录下包含 Makefile 文件&#xff0c;执行 make qemu 即可。 sleep 思路 借助系统调用 sleep 实现一个命令行程序&#xff0c;关键是要找到封装了系统调用的 C 函数的位置&#xff0c;根据提示&#xff1a; … user/u…

物联网系统运维——实验备份与恢复,数据镜像软件DRBD介绍,DRBD的安装和应用,extundelete的安装和应用(重点),环境准备,配置设置

一.数据备份 1.数据备份的重要性 备份是系统中需要考虑的最重要的事项,虽然这在系统的整个规划,开发和测试过程中甚至占不到1%,看似不太重要且默默无闻的工作只有到恢复的时候才能真正体现出其重要性,任何数据的丢失与数据宕机&#xff0c;都是不可以被接收的。 2.数据备份策…

oracle 11g rac安装grid 执行root脚本add vip -n 。。。on node= ... failedFailed 错误处理

问题&#xff1a; CRS-4402: The CSS daemon was started in exclusive mode but found an active CSS daemon on node racdg1-1, number 1, and is terminating An active cluster was found during exclusive startup, restarting to join the cluster PRCN-2050 : The requ…

程序员学长 | 快速学会一个算法,Transformer(下)

本文来源公众号“程序员学长”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;快速学习一个算法&#xff0c;Transformer&#xff08;二&#xff09; 今天我们来继续分享 Transformer 模型的第二部分&#xff0c;解码器部分。 建…

热虹吸管的传热计算

热对称管和热管通过使用中空管内的两相流体&#xff0c;在特定的距离上传输大量的热量。 更广泛使用的热管使用吸芯结构将液体输送回热端&#xff0c;而热虹吸管是一个简单的空心管&#xff0c;使用重力。 由于缺乏吸芯结构&#xff0c;使得热虹吸管比传统的热管便宜得多。 然…

自学指南:必备书籍清单--近100本R语言及生物信息相关书籍

R语言是一种功能丰富的编程语言&#xff0c;数据处理、统计分析是大家所熟知的基本功能。开源免费、活跃的全球社区、灵活可扩展等优点促使R语言飞速发展。目前&#xff0c;CRAN 软件包存储库包含 20446 个可用软件包&#xff0c;涵盖了从生物信息到金融分析等广泛的应用领域。…

vue3.0(十五)内置组件Teleport和Suspense

文章目录 一、Teleport1.基本用法2.禁用Teleport3.多个 Teleport 共享目标4.搭配组件 二、 Suspense1.什么是Suspense2.异步依赖3.加载中状态4.事件5.错误处理6.和其他组件结合注意 一、Teleport <Teleport> 是一个内置组件&#xff0c;它可以将一个组件内部的一部分模板…

第二十八篇——复盘:世界不完美,我们该怎么办?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 对于信息传递过程中的相关知识的总结&#xff0c;让我又仿佛回到了每一个…