线程池,字面意思来说,有点类似与我们的字符串常量池,数据库连接池等等。
1.1 为啥要引入线程池这一概念呢??
🍃这得从引入线程说起,,
🍃进程本身已经能做到 并发编程了,为啥还要有线程??进程太重量了,创建和销毁成本都比较高(需要申请、释放资源),而线程就是针对上述问题的优化(同一个进程中的线程共用同一组系统资源)
🍃虽然如此,但是在更频繁的创建、释放资源的场景下,线程也有点扛不住了!!
所以,进一步优化,就引入了线程池。线程池存在的目的,就是为了让程序猿不必创建新的线程,直接使用已有的线程完成想要进行的工作即可!!
🍁【线程池解决问题的思路】
把线程创建好了之后,放到池子里,需要使用线程,就直接从池子里取,而不是通过系统来创建。当线程用完了,又放回池子里,而不是通过系统来销毁!!
1.2 为什么把线程放到池子里,从池子里取线程就要比从系统这里创建线程效率高呢??
🍃1.从池子里取,是纯用户态操作。
🍃2.通过系统来创建,涉及到内核操作。
而我们通常认为,牵扯到内核态操作,就要比纯用户态的操作更低效!!结合下图场景理解
🍃1.使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
🍃2.返回值类型为 ExecutorService🍃3.通过 ExecutorService.submit 可以注册一个任务到线程池中 .
- public static void main(String[] args) {
- ExecutorService threadPool = Executors.newFixedThreadPool(10);
- for(int i = 0; i < 100; i++) {
- threadPool.submit(new Runnable() {
- @Override
- public void run() {
- System.out.println("hello");
- }
- });
- }
- }
注意这行代码:
ExecutorService threadPool = Executors.newFixedThreadPool(10);
newFixedThreadPool 是 Executors 类的一个静态方法,像这种借助静态方法创建实例,这样的方法,称为"工厂方法",对应的设计模式,就叫做"工厂模式"。
通常情况下,创建对象,是借助 new ,调用构造方法来实现的。但是 Java 里面的构造方法,有诸多限制,在很多时候不方便使用,因此就需要给构造方法再包装一层,外层起到包装作用的方法就是工厂方法!!
🍃为什么构造方法有时候不方便使用??
构造方法的限制在于,当前构造方法的名字务必是和类名一样。要想实现不同版本的构造,就得通过重载,但是重载又要求参数类型和个数不同。当我们的场景刚好是参数类型相同且参数个数相同的时候,重载就行不通了!!请看以下例子:
当我需要用构造方表示两个点:一个是笛卡尔坐标系,构造点;一个是极坐标系构造点。
- class Point {
- // 笛卡尔坐标系,构造点
- public Point(double x, double y) { };
- // 使用极坐标系,构造点
- public Point(double r, double a) { };
-
- // Error : 编译错误
- }
显然,这段代码编译错误,不符合重载规则,这时候我们就需要使用工厂模式来解决上述问题了。
- class Point {
- // 笛卡尔坐标系,构造点
- public static Point makePointByXY(double x, double y) {
- Point point = new Point();
- point.setX(x);
- point.setY(y);
- return point;
- }
- // 极坐标系,构造点
- public static Point makePointByRA(double r, double a) {
- Point point = new Point();
- point.setR(r);
- point.setA(a);
- return point;
- }
- }
于是此时实例化只需要通过类名调用需要的静态方法即可创建!!
🍁Executors 创建线程池的几种方式:
🍃1.newFixedThreadPool: 创建固定线程数的线程池🍃2.newCachedThreadPool: 创建线程数目动态增长的线程池 .🍃3.newSingleThreadExecutor: 创建只包含单个线程的线程池 .🍃4.newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令 . 是进阶版的 Timer.
Executors 里面的各种工厂方法,其实都是针对 ThreadPoolExecutor 这个类进行了 new 并且闯入不同风格的参数,来达到构造不同种类线程池的目标!!
- class MyThreadPool {
- // 这个队列就是 "任务队列" 把当前线程要完成的任务都放入到这个队列中
- // 再由线程池内部的工作线程负责完成它们!!
- private BlockingQueue
queue = new LinkedBlockingQueue<>(); -
- // 核心方法,往线程池里插入任务
- public void submit(Runnable runnable) {
- try {
- queue.put(runnable);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- // n : 设定线程池里有几个线程
- public MyThreadPool(int n) {
- // 构造方法中就需要创建一些线程,让这些线程负责执行上述插入的任务
- for(int i = 0; i < n; i++) {
- Thread t = new Thread(() -> {
- while(!Thread.currentThread().isInterrupted()) {
- try {
- // 取出任务,直接执行
- Runnable runnable = queue.take();
- runnable.run();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- t.start();
- }
- }
- }
- public class TestDemo3 {
- public static void main(String[] args) {
- // 10 个线程一起执行 100 个任务
- MyThreadPool myThreadPool = new MyThreadPool(10);
- for(int i = 0; i < 100; i++) {
- myThreadPool.submit(new Runnable() {
- @Override
- public void run() {
- System.out.println("hello");
- }
- });
- }
- }
- }
线程池的实现相比于定时器,要简单太多了!!
🍁【基本步骤】
🍃1.核心操作为 submit,将任务加入线程池中;
🍃2.使用一个 BlockingQueue 组织所有的任务;
🍃3.每个线程,不停的从 BlockingQueue 中取任务并执行即可。
本篇博客就到这里了,谢谢观看!!