本文主要给大家讲解多线程的一个重要案例 — 线程池.
关注收藏, 开始学习吧🧐
在讲解线程池是什么之前, 我们先简单聊一聊 “池” 的概念, 在我们学习中, “池” 是一个非常重要的思想方法, 之前听过有内存池, 进程池, 连接池, 常量池等等, 这里的 “池” 其实本质概念上都是一样的.
那么什么是 “池” 呢, 不站在道德层面上来讲, 其实就是我们常说的鱼塘, 鱼塘里都是鱼, 也就是常听到的 “备胎”, 这样就容易理解了吧? 同时和池子里的多个目标搞暧昧, 也就是扩大备胎池, 是不是在某种意义上就提高了谈恋爱的效率呢.
在这里我们的线程池也是一样的, 如果我们只创建销毁一个线程的话, 成本可能并不高, 当我们需要频繁的创建 / 销毁线程, 此时创建销毁线程的成本就不能被忽视了, 因为数量太多了. 我们就需要线程池了. 我们提前创建好一些线程放在一个池子里, 当我们后续需要使用线程时, 直接从池子里拿即可, 当线程不再使用时, 就放回池子里, 就可以大大减少我们频繁创建 / 销毁线程的成本.
那么从线程池里取, 就比从系统这里创建线程更高效吗?
在 Java 标准库中, 也提供了现成的线程池.
public class ThreadDemo22 {
// 线程池
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
// 注册 1000 个任务到线程池中
for (int i = 0; i < 1000; i++) {
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
}
}
}
可以看到, 在创建线程池时, 并不是使用 new 一个对象来进行实例化的. 使用的是一个工厂方法 Executors.newFixedThreadPool()
, 而 Executors
则是工厂类.
我们来简单谈谈设计模式中的一个经典模式 ---- 工厂模式.
顾名思义, 工厂就是用来生产的, 是用来生产对象的, 一般我们创建对象时, 都是使用 new, 通过构造方法来实例化一个对象, 但其实 Java 中的构造方法, 存在一个问题.
构造方法的名字固定就是类名, 而有的类, 需要有多种不同的构造方式, 但是构造方法名字又是固定的, 就只能使用方法重载的方式来实现了 (方法名相同, 参数个数和类型不同). 这里我给大家举个例子.
当我们想要描述一个点时, 我们想按照两种方式进行构造, 一种是按照笛卡尔坐标构造(提供 x, y), 一种是按照极坐标系构造 (提供距离坐标原点距离 r, 以及点与原点连线和 x 轴形成的角度 a), 这两种构造方式, 参数的个数和类型是一样的, 就无法构成重载.
class Point { public Point(double x, double y) {} public Point(double r, double a) {} }
- 1
- 2
- 3
- 4
- 5
方法名相同, 参数个数和类型也相同, 无法构成重载.
此时就可以使用工厂模式来解决以上问题, 不使用构造方法, 而是使用普通的方法来构造对象, 这样方法名字就可以是任意的了. 在普通方法内部, 再来 new 对象, 要注意, 这里的普通方法目的是为了创建出对象来, 所以工厂方法一般都得是静态的.
// 工厂模式
class Point {
// 工厂方法
public static makePointXY(double x, double y) {
// new进行实例化对象
}
// 工厂方法
public static makePointRA(double r, double a) {
// new进行实例化对象
}
}
我们继续来讲解标准库中的线程池, Executors
主要有以下几种创建的方法.
newFixedThreadPool()
: 创建一个固定线程数的线程池.newCachedThreadPool()
: 创建线程数目动态增长的线程池.newSingleThreadExecutor()
: 创建只包含单个线程的线程池.newScheduledThreadPool()
: 设定延迟时间后执行命令,或者定期执行命令. 可以看成是进阶版的定时器 Timer.其实 Executors
本质上是对 ThreadPoolExecutor
类进行的封装. ThreadPoolExecutor
提供了更多的可选参数 (接口更加丰富), 可以进一步细化线程池行为的设定, 更好的满足开发时的实际需求.
这个是 ThreadPoolExecutor
类中参数最全的一个构造方法.
int corePoolSize
, int maximumPoolSize
: 前者代表核心线程数, 后者代表最大线程数. ThreadPoolExecutor 里面的线程个数, 并非是固定不变的, 会根据当前任务的情况自适应动态变化. 核心线程数表示, 至少得有这些线程, 即使线程池中一点任务也没有. 而最大线程数则表示, 最多不能超过这些线程, 即使线程池中任务已经很多了, 忙不过来了, 也不能比这个数目多. 这样可以做到, 既能保证繁忙的时候可以高效处理任务, 又能保证空闲的时候不会浪费多余资源.long keepAliveTime
, TimeUnit unit
: 前者表示当没有任务时, 允许线程空闲的最大时间, 空闲时间超过指定值, 线程就可以被销毁了. 后者表示该空闲等待时间的单位.BlockingQueue workQueue
: 线程池内部有很多任务, 这些任务, 可以使用一个阻塞队列来管理. 线程池可以内置阻塞队列, 也可以自己手动指定一个.ThreadFactory threadFactory
: 工厂模式, 通过这个工厂类来创建线程.RejectedExecutionHandler handler
: 拒绝方式 / 拒绝策略, 是线程池考察的重点. 当线程池中阻塞队列满了之后, 在继续添加任务时, 该如何应对.上面我们谈到的线程池, 一组是被封装过的 (Executors), 一组是原生的 (ThreadPoolExecutor), 在开发过程中, 用哪个都可以, 主要是看公司要求, 以及实际需求.
class MyThreadPool {
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
public MyThreadPool(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) {
throw new RuntimeException(e);
}
}
});
t.start();
}
}
}
✨ 本文主要讲解了什么是线程池, 使用了标准库中的线程池, 简单聊了工厂模式, 以及线程池中的几个参数, 最后自己实现了一个线程池.
✨ 想了解更多的多线程知识, 可以收藏一下本人的多线程学习专栏, 里面会持续更新本人的学习记录, 跟随我一起不断学习.
✨ 感谢你们的耐心阅读, 博主本人也是一名学生, 也还有需要很多学习的东西. 写这篇文章是以本人所学内容为基础, 日后也会不断更新自己的学习记录, 我们一起努力进步, 变得优秀, 小小菜鸟, 也能有大大梦想, 关注我, 一起学习.
再次感谢你们的阅读, 你们的鼓励是我创作的最大动力!!!!!