线程池
如果我们需要频繁的创建销毁线程,此时创建销毁线程的成本,就不能忽视了.因此就可以使用线程池.即,提前搞好一波线程,后续需要使用线程就直接从池子里拿一个即可.当线程不再使用,就放回池子里.
本来,是需要创建线程/销毁线程.现在是从池子里获取到现成的线程,并且把线程归还到池子中.
那么,为什么从池子里获取,就比从系统这里获取更高效呢?
如果是从系统这里创建线程,需要调用系统API,进一步的由操作系统内核,完成线程的创建过程(内核是给所有的进程提供服务的)是不可控的
如果是从线程池这里获取线程,上述的内核中进行的操作,都提前做好了,现在的取线程的过程,纯粹的用户代码完成(纯用户态)可控的.
也正是由于内核是给所有进程提供服务的,所以从线程池中获取的方式更快.
打个比方,就比如说你要去打印店打印
1.让柜员帮你复印:此时柜员消失在你的视野中之后可能会去做很多事情(无法掌控)
2.自己去自主复印机上复印:立即去这里复印,就可以立即把复印件拿回来(自主可控)
当然了,在Java标准库中,也提供了现成的线程池来供我们使用
好!,在上述图中出现了两个新名词:工厂类与工厂方法
此时就涉及到了工厂模式
工厂模式
工厂=>生产
生产对象的
一般创建对象,都是通过 new ,通过构造方法.但是构造方法,存在重大缺陷(构造方法的名字固定就是类名)此时就可以使用工厂模式来解决上述问题了.
就比如:有的类,需要有多种不同的构造方式,但是构造方法的名字又是固定的,此时就只能使用方法重载的方式来实现了.(方法重载的前提:参数的个数和类型需要有差别)
就比如我们想要用笛卡尔坐标系以及极坐标系来表示一个点,想要用这两种方式进行构造,而这两种构造方式,参数的个数和类型是一样的!!!无法构成重载!!!.此时上述代码就会报错
使用工厂模式来解决上述问题.不使用构造方法了,使用普通的方法来构造对象.这样的方法名字就可以是任意的了.在普通方法内部,再来new对象,而且由于普通方法的目的是为了创建出对象来,这样的方法一般得是静态的
再次注意:使用static修饰的方法叫做类方法,使用static修饰的变量叫做类的成员变量,无论是类方法还是成员变量,都是可以直接通过类名来直接访问的.
此时上述的操作就叫做工厂模式,上述这两个普通的类方法就叫做工厂方法.
创建一个固定线程数量的线程池
创建出一个线程数目动态变化的线程池
创建出一个只包含单个线程的线程池(比原生的创建线程API更简单一点)
这个就类似与我们的定时器的效果,即添加一些任务,任务都在后续的某个时刻再执行.被执行的时候不是只有一个扫描线程来执行任务,可能是由多个线程共同执行所有的任务.
线程池对象搞好了之后,使用submit方法,就可以把任务添加到线程池中.
当然,既然是线程池,那创建一个任务就没意思了
除了上述这些线程池之外,标准库还提供了一个接口更丰富的线程池类
关于这串代码虽然写的是不超过4个线程,但是仍然可以在这运行100个这件事
多尝试看看源码,就会解决很多问题
ThreadPoolExecutor
这个其实是Java原生的线程池接口
其实这些东西都是为了使用方便已经去做了一层封装了,如果说你发现这样线程池不能很好的满足你的需求,那你也就可以直接去使用系统原生的线程池接口.
ThreadPoolExecutor
这个原生接口的好处就是有很多可以供我们调整的选项,就可以更好的满足我们的实际需求
经典面试题:谈谈Java标准库中的线程池的构造方法的参数和含义
int corePoolSize 核心线程数
int maximuPoolSize 最大线程数
ThreadPoolExecutor 里面的线程个数,并非是固定不变的,会根据当前任务的情况动态发生变化(自适应)
corePoolSize:至少得有这些线程,哪怕你的线程池一点任务都没有.
maximumPoolSize:最多不能超过这些线程,哪怕你的线程池忙的冒烟了,也不能比这个数目更多了.
如果把线程池比作一个公司,那么核心线程数,就是正式员工的数量.最大线程数就是正式员工+实习生的数量.当公司不忙的时候就不需要实习生,当公司业务繁忙的时候,就可以招一些实习生来分担业务,过一段时间,业务又不忙了,实习生就可以裁掉了
long keepAliveTime:这里输入数字
TimeUnit unit:这里输入单位
当有线程超过long keepAliveTime与TimeUnit unit所设定的时间阈值,那么这个线程就可以被销毁了.
BlockingQueue workQueue:管理线程池内部多个任务的阻塞队列.
线程池是内置阻塞队列的,但也可以由我们手动指定一个阻塞队列.
ThreadFactory threadFactory:工厂类,我们可以通过这个工厂类来创建线程