本文主要给大家讲解多线程的一个重要案例 — 线程池.
关注收藏, 开始学习吧
在讲解线程池是什么之前, 我们先简单聊一聊 “池” 的概念, 在我们学习中, “池” 是一个非常重要的思想方法, 之前听过有内存池, 进程池, 连接池, 常量池等等, 这里的 “池” 其实本质概念上都是一样的.
那么什么是 “池” 呢, 不站在道德层面上来讲, 其实就是我们常说的鱼塘, 鱼塘里都是鱼, 也就是常听到的 “备胎”, 这样就容易理解了吧? 同时和池子里的多个目标搞暧昧, 也就是扩大备胎池, 是不是在某种意义上就提高了谈恋爱的效率呢.
在这里我们的线程池也是一样的, 如果我们只创建销毁一个线程的话, 成本可能并不高, 当我们需要频繁的创建 / 销毁线程, 此时创建销毁线程的成本就不能被忽视了, 因为数量太多了. 我们就需要线程池了. 我们提前创建好一些线程放在一个池子里, 当我们后续需要使用线程时, 直接从池子里拿即可, 当线程不再使用时, 就放回池子里, 就可以大大减少我们频繁创建 / 销毁线程的成本.
那么从线程池里取, 就比从系统这里创建线程更高效吗?
在 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) {} }
方法名相同, 参数个数和类型也相同, 无法构成重载.
此时就可以使用工厂模式来解决以上问题, 不使用构造方法, 而是使用普通的方法来构造对象, 这样方法名字就可以是任意的了. 在普通方法内部, 再来 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();
}
}
}
✨ 本文主要讲解了什么是线程池, 使用了标准库中的线程池, 简单聊了工厂模式, 以及线程池中的几个参数, 最后自己实现了一个线程池.
✨ 想了解更多的多线程知识, 可以收藏一下本人的多线程学习专栏, 里面会持续更新本人的学习记录, 跟随我一起不断学习.
✨ 感谢你们的耐心阅读, 博主本人也是一名学生, 也还有需要很多学习的东西. 写这篇文章是以本人所学内容为基础, 日后也会不断更新自己的学习记录, 我们一起努力进步, 变得优秀, 小小菜鸟, 也能有大大梦想, 关注我, 一起学习.
再次感谢你们的阅读, 你们的鼓励是我创作的最大动力!!!!!