JavaEE:线程池精讲

目录

一.什么是线程池

二.线程池的实现原理

🎈为什么要有工厂模式?

三.线程池的构造方法解读

🎈线程池的拒绝策略

四.自己实现一个线程池


一.什么是线程池

简单来说,线程池就好比一块鱼塘,鱼塘中的每条鱼就是一个线程。那么为什么要有这个线程池呢?就好比 一个"渣女\渣男",当他和A在一起的时候,如果想和B在一起,那么就需要先想办法和A分手,再和B搞好关系,最终和B在一起。如果她和A谈的时候,已经找好了B C D,此时就可以直接拿来无缝衔接~~

其实线程池也就大概这个作用,里面存放一些线程,需要用的时候直接拿来使用。

二.线程池的实现原理

我们先来看线程池是如何创建的:

java 复制代码
package Pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class threadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();
    }
}

很明显,此处的线程池竟然不是 new 出来的,那么它是如何被实例化的呢?其实这里就使用了一种设计模式:工厂模式。

🎈为什么要有工厂模式?

其实工厂模式就时给Java中的构造方法填坑的,我们的构造方法其实时有很大缺陷的,我们来看以下例子。

我们期望这个Point类,初始化的时候能够传入 double x,double y,用来构造笛卡尔坐标系

我们又希望在不创建其他类的情况下,这个Point类, 初始化的时候能够传入 double r,double a,用来构造极坐标系。

但是构造方法也是方法,此时的方法由于参数个数和参数的类型都相同,就会编译失败,那么就没办法满足期望!

此时我们可以再写一个类:

此时这个类中又两个静态方法,一个是构造笛卡尔坐标系,并且返回。一个是构造及坐标系,并且返回对象。

那么我们就可以使用以下语句来分别调用:

Point p1 = PointFactory.makePintByXY(10,20)

Point p2 = PointFactory.makePintByRA(12,63)

此时通过PointFactory类,来给Point类传入需要的值就可以了。


那么线程池也是通过这样的方式来进行创建的:

三.线程池的构造方法解读

从Excutor这个工厂类的源码中可以得到以下:

其实线程的创建又被封装到了一个叫做ThreadPoolExecutor的类中

点开ThreadPoolExecutor,可以得到如下图片:

其中的每个参数的意思是这样的:

第一个是核心线程数, 第二个是最大线程数

线程池中的线程数目是可以动态变化的

范围就是【int corPoolSize. ~ int maxmumPoolSize】

什么是核心线程:

就好比一个公司中有正式员工(核心线程)和实习生,总的员工数目不能超过一定的值。当人手不够用就招实习生,这样既可以满足效率的需求,又可以避免过多的开销。

第三个是线程的可存活时间

第四个TimeUnit unit是用来设置非核心线程闲置超时时长(keepAliveTime)的单位。当一个非核心线程的闲置时间超过这个参数所设定的时长时,该线程就会被销毁掉。

第五个比较重要

第六个是线程池的拒绝策略,也就是当所有线程都处于忙碌状态,如果还往线程池中添加元素,线程池所做的操作。

这些构造方法,第一个和第五个以及第六个是需要重点掌握的。

下面来单独讲讲第六个参数:拒绝策略

🎈线程池的拒绝策略

所谓的拒绝策略,其实就是如果线程池中每个线程都是处于忙碌的状态,如何应对新来的线程任务。

举个例子:如果我周一到周五都是满课,此时我一朋友让我给他去代课,那么此时我如何应对?此时就会有相应的应对策略:

  1. AbortPolicy (默认策略):这是默认的拒绝策略,它会抛出一个未检查的RejectedExecutionException,以指示任务被拒绝。也就是我本来都满课了,朋友还让我去代课,此时我就直接崩溃,代课和我自己的课我都不去上了,直接崩溃!
  2. CallerRunsPolicy:这个策略不会抛出异常。相反,它会将任务退回给调用线程,让它自己运行这个任务。 也就是让我朋友自己去上课。
  3. DiscardOldestPolicy:此策略会丢弃队列中等待最久的任务,并立即返回给调用者。也就是我丢弃我课程中一节课,去给他代课。
  4. DiscardPolicy:这个策略会静默地丢弃被拒绝的任务。也就是说,它不会抛出任何异常,也不会通知任务被拒绝。 也就是我拒绝去给他代课,我自己上自己的课。然后我朋友也不去上课了,那么这个课(任务) 也就黄了。

四.自己实现一个线程池

实现线程池一个最关键的步骤就是拒绝策略,那么说明拒绝策略呢?

由于之前学过阻塞队列的知识,这里就先用阻塞队列来实现以下。

那么这个就是一种自己定义的新的拒绝策略,那就是一直等待~

java 复制代码
package Pool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;


class MyPoolDemo {
    //一个队列
    BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);

    //通过这个方法,把任务添加到队列当中
    public void submit(Runnable task) throws InterruptedException {
        queue.put(task); //往阻塞队列中放入元素
    }

    public MyPoolDemo(int n) throws InterruptedException {
        //构造方法
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
               //让这个线程从队列中消费任务并且进行执行
                try {
                    //如果队列中没有元素,那就阻塞等待
                    //一旦队列中有了任务,那么就立即执行take方法获取到任务并且开始执行
                        Runnable task = task = queue.take();
                        task.run();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();

        }
    }


}
public class RunDemo {
    public static void main(String[] args) throws InterruptedException {
        /**
         * 步骤理解:
         * 1.创建线程池并且指定线程数目是 20 ,在实例化线程池的时候已经创建好了20个线程
         * 2.这20个线程都在等待 take 获取到任务队列中的任务
         * 3.for 语句 循环 1000次,每次循环都会提交任务到任务队列
         * 4.一但任务队列里面有元素,这20个线程就会立马获取到,并且执行
         */
        MyPoolDemo myPoolDemo = new MyPoolDemo(20);
        int taskCount = 1000;
        while (true) {
            for (int i = 0; i < taskCount; i++) {
                int id = i;
                myPoolDemo.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("执行任务:" + id);
                    }

                });
            }
        }
    }
    }

运行结果:

代码解读:

1.线程池里的线程是需要执行任务的,这个任务可以放到 BlockingQueue 这个阻塞队列中。为什么要使用阻塞队列呢?当线程池中的线程都在工作,此时就直接等待阻塞。

2.submit 方法接受一个实例化好的Runnable类型的任务,负责往队列中添加元素

  1. MyPoolDemo(int n) 是这个类的构造放法,当实例化这个类的时候,被指定的 n 就是要创建的线程数量。

  2. 由运行结果可以得出:当线程数量为20的时候,可以看到任务被随即执行完了。


总结:Java线程池是Java并发编程中一个重要的概念,它用于管理和控制线程的创建、销毁,以及任务提交和执行。线程池的主要目的是减少创建和销毁线程的开销,提高性能。

相关推荐
一勺菠萝丶8 分钟前
PDF24 转图片出现“中间横线”的根本原因与终极解决方案(DPI 原理详解)
java
姓蔡小朋友12 分钟前
Unsafe类
java
一只专注api接口开发的技术猿25 分钟前
如何处理淘宝 API 的请求限流与数据缓存策略
java·大数据·开发语言·数据库·spring
superman超哥26 分钟前
Rust 异步递归的解决方案
开发语言·后端·rust·编程语言·rust异步递归
荒诞硬汉26 分钟前
对象数组.
java·数据结构
期待のcode28 分钟前
Java虚拟机的非堆内存
java·开发语言·jvm
黎雁·泠崖28 分钟前
Java入门篇之吃透基础语法(二):变量全解析(进制+数据类型+键盘录入)
java·开发语言·intellij-idea·intellij idea
仙俊红31 分钟前
LeetCode484周赛T4
java