自建站跨境电商,电信宽带多少钱,怎样用word2003做网站,网络架构师主要做什么文章目录 一、线程池概念(一)什么是线程池(二)为什么认为直接创建比在池子中取线程开销更大 二、标准库中的线程池 ThreadPoolExecutor(一)构造方法(二)七个参数含义(后四个重点掌握)(三)工厂模式1) 典型案例2) 使用工厂模式 (四)拒绝策略为什么使用线程池而不使用生产者消费者… 文章目录 一、线程池概念(一)什么是线程池(二)为什么认为直接创建比在池子中取线程开销更大 二、标准库中的线程池 ThreadPoolExecutor(一)构造方法(二)七个参数含义(后四个重点掌握)(三)工厂模式1) 典型案例2) 使用工厂模式 (四)拒绝策略为什么使用线程池而不使用生产者消费者模型 一、线程池概念
回顾一下,我们最初引入线程的原因:频繁创建销毁进程,太慢了,而且开销很大 同样的,随着互联网的发展,随着我们对于性能要求更进一步~,频繁创建销毁线程,开销有些不能接受了
那我们为了更高效的创建销毁线程,解决方案有两个:1.线程池2.协程(纤程,轻量级线程)
我们本篇文章重点介绍线程池
关于协程我们现在只做简单了解
协程Java17左右的时候,才引入到标准库中,目前在Java圈子还没有普遍使用Go语言更擅长处理并发编程使用协程,相比于线程性能更高
线程池是一种池化技术,用于预先创建并管理一组线程,避免频繁创建和销毁线程的开销,提高性能和响应速度
(一)什么是线程池 场景: 学校新开了一家快递店,老板灵机一动,想了个与众不同的办法来经营店面,店里没有雇人,而是每次有业务,就现场找一名同学把快递送了,然后解雇同学 这就像我们平时处理一个任务,创建一个线程进行处理的模式 很快就发现了问题,每次招聘解雇同学的成本还是很高的,所以老板指定了一个指标,业务人员扩张到3人,但是还会随着业务的扩大逐步招人,于是再有业务,如果公司没有3个人,就雇1个人去送快递,否则,只是吧业务放到一个本本上,等着3个员工空闲的时候去处理,这个就是我们的线程池模式 其实我们早就接触到了池的概念
常量池字符串常量,在Java程序最初构建的时候,就已经准备好,等程序运行的时候,这样的常量也就加载到内存中了,省下了很大构造/创建的开销字符串常量池,线程池,进程池,内存池,数据库连接池…就像是一池子备用胎,现在虽然没有,随时要拿出来使用
简单来说线程池:把线程提前创建好,放到一个地方(类似于数组),需要用的时候,随时去取,用完了还回到池子中
(二)为什么认为直接创建比在池子中取线程开销更大 操作系统中包含用户态 和 内核态 一个操作系统 内核 配套的应用程序内核 中包含操作系统的各种核心功能1)管理硬件设备2)给软件提供稳定的运行环境 一个操作系统,内核就是一份,一份内核要给所有的应用程序提供服务支持 如果有一段代码是应用程序中自行完成,整个执行过程是可控的 如果有一段代码,需要进入到内核中,由内核负责完成一系列工作,这个过程是不可控的,程序员写的代码干预不了因此通常认为,可控的过程要比不可控过程更高效~~
从线程池中取现成的线程,纯应用程序代码就可以完成 [可控] 从操作系统创建新线程,就需要操作系统内核配合完成 [不可控] 使用线程池,就可以省下应用程序切换到内核中运行这样的开销
二、标准库中的线程池 ThreadPoolExecutor
线程池的核心方法,submit(Runnable)通过Runnable描述一段要执行的任务通过submit任务放到线程池中此时线程池里的线程就会执行这样的任务
(一)构造方法 (二)七个参数含义(后四个重点掌握) Java的线程池,里面包含几个线程,是可以动态调整的,任务多的时候,自动扩容成更多的线程,任务少的时候 ,把额外的线程干掉,节省资源 1.int corePoolSize 核心线程数 线程池中至少有多少个线程,线程池一创建,这些线程也要随之创建,直到整个线程池销毁,这些线程才会销毁 2.int maximumPoolSize 最大线程数 核心线程非核心线程(自适应) 若任务并不繁忙就会销毁,繁忙就再创建,线程也不是越多越好,毕竟线程太多会抢占资源 3.long keepAliveTime 非核心线程允许空闲的最大时间 不能这个线程一空闲下来,就直接把它销毁,因为可能下一秒就需要他干活啦,我们为他设置一个最大的摸鱼时间~~ 4.TimeUnit unit 是一个枚举类型的参数作为keepAliveTime 的时间单位是秒分钟或者其他值 5.BlockingQueue workQueue : 工作队列 我们可以选择使用数组/链表 指定capacity 指定是否要带有优先级/比较规则 泛型为Runnable ,说明阻塞队列中的元素是一个个Runnable任务 每次通过调用 submit() 方法,submit就会将任务添加到队列(阻塞队列) 因为需要以阻塞队列为基础,进行数据交互,所以工作队列的类型为BlockingQueue 线程池本质上也是生产者消费者模型,调用submit就是在生产任务,线程池里的线程就是在消费任务
6. ThreadFactory threadFactory : 线程工厂 统一的构造并初始化线程 利用工厂模式来弥补构造方法的缺陷(无法构成重载)
(三)工厂模式
是一种创建型的设计模式,提供了一种封装对象创建过程的方法工厂模式通过静态方法,将对象的创建(实例化 初始化) 将创建对象的过程封装好,弥补构造方法的缺陷有助于降低代码的耦合度,提高可维护性和可扩展性
1) 典型案例 这是 C 和 Java共有的一个问题构造方法的名字是固定的,想要提供不同的版本,需要通过重载来实现,但是像上述情况就无法构成重载,需要借助工厂模式实现
2) 使用工厂模式 其中 用来构造对象的静态方法(makePointByXY makePointByRA) 称为工厂方法提供工厂方法的类(PointFactory) 称为工厂类
通过单独的类(PointFactory)提供工厂方法(makePointByXY makePointByRA),和构造对象的类(Point)分开,这是更科学的工厂设计模式
工厂方法的核心:通过静态方法,把构造对象new的过程,各种属性初始化的过程,封装起来,提供多组静态方法,实现不同情况的构造
我们继续回到第六个参数ThreadFactory threadFactory : 线程工厂
此外如果想让线程池中的线程具有其他的特性,或者想去干预这些线程的其他功能,都可以手动实现 ThreadFactory接口,重写newThread()方法
class SimpleThreadFactory implements ThreadFctory{public Thread newThread(Runnable r){return new Thread(r);}
}标准库通过这个方法,帮我们设置好线程池中基本的线程属性 7.RejectedExecutionHandler handler : 拒绝策略 整个线程池七个参数中,最重要,最复杂的
(四)拒绝策略
RejectedExecutionHandler handler 1) AbortPolicy --异常,线程池可能无法继续工作 当任务队列满且没有线程空闲,此时添加任务会直接抛出RejectedExecutionException异常,这也是默认的拒绝策略 非但新的任务干不了,甚至之前旧的任务也干不了了~ 2) CallerRunsPolicy --让调用者自己干 当任务队列满且没有线程空闲,此时添加任务由调用者线程即调用submit的线程来执行该任务 也就是说submit整个方法里的代码,都是在这个线程中执行的 submit方法里,暗藏玄机~ 判定当前队列是否满,判定当前拒绝策略是否为CallerRunsPlicy ,如果两个判断都为true submit内部就会直接调用 Runnable,run()
伪代码:
void submit(Runnable runnable){if(队列满了 拒绝策略为CallerRunsPlicy){runnable.run();return ;}queue.put(runnable);//若不-线程池里的一组线程来执行任务return ;
}一个进程中会有很多个线程,假设线程池中已经有4个线程(a,b,c,d),现在来了一个新的线程X,调用submit方法 此时X调用submit,就是执行上述的伪代码逻辑,X就是调用者线程 3) DiscardOldestPolicy --丢弃最老的任务,干当前给的新任务 当任务队列满且没有线程空闲,会删除最早的任务,处理最新的任务 4) DiscardPolicy --丢掉当前的新任务 直接丢弃当前给的这个新任务,不会执行任何操作,也不会抛出异常 自定义拒绝策略
为什么使用线程池而不使用生产者消费者模型
在了解了拒绝策略后,再做一个扫盲,我们前面说,线程池的本质就是生产者消费者模型,那么为什么我们不直接使用生产者消费者模型
submit把任务添加到任务队列中,这个任务队列就是阻塞队列队列满了,再添加,就会阻塞,但是我们一般不希望程序阻塞太多对于线程池来说,入队列时发现队列满了,不会真的触发入队列操作,不是真的阻塞,而是执行拒绝策略的相关代码如果业务逻辑中的线程调用submit就阻塞,就会使这个线程无法干别的事情了,明显不是一个好的选择线程响应用户的请求阻塞了,用户迟迟拿不到响应,直观上的现象是卡了,其实是失败了,与其告诉我卡了,不如直接告诉我失败了
线程池最大的好处就是减少每次启动,销毁线程的损耗