多线程(七)【线程池】

上篇文章我为大家归纳总结什么是阻塞队列,如何使用阻塞队列并且我们自己手动模拟实现了阻塞队列~~~这边文章,我将继续归纳总结在多线程中,关于线程池的概念,线程池如何使用,然后我们也继续自己手动模拟实现一个简单版本的线程池~~~

1.什么是线程池

2.刨析线程池构造方法中的对象

3.线程池的使用

4.手动模拟实现线程池

什么是线程池

我们可以想象现在建一个池子,建好池子后,我们创建数个线程,放在池子里,当我们任务队列中有任务的时候,就不需要创建线程,可以直接让线程池中创建好的线程直接去执行任务,就节省了一部分时间上的开销,提高效率,避免短时间内大量的进行线程的创建和销毁,

采用线程池的方案,提前把线程通过操作系统的API创建好,放在一个集合类中,后续如果需要使用,就直接从集合类中获取

总结来说:把线程提前的创建好,放在一个固定的地方,需要用到的时候就可以直接获取

这样的操作比操作系统创建来的就更快~~~避免创建线程和销毁线程的频繁开销

Java官方标准库中也提供了现成的线程池,我们需要对官方提供的线程池中的构造对象进行了解~

刨析线程池中构造方法的对象

1.)核心线程数

当线程池被创建的时候,就已经创建好了的线程,不会被销毁---- 核心线程数

后续发现线程池中的线程数不够了,但又由于任务太多,就会额外创建线程,可能会被销毁 ---- 非核心线程数

所以在线程池中涉及到俩类线程 ,并且会自动扩容(通过非核心线程数)

当任务多的时候,线程就多创建点;当任务少的时候,线程就少创建点~~~~

2.)最大线程数

最大线程数 = 核心线程数(线程池创建的时候就创建的) + 非核心线程数(后来创建的)

3.)非核心线程允许空闲的最大时间

当任务减少时,非核心线程就可能处于空闲的状态,这时,操作系统并不会立即将这些空闲的非核心线程销毁掉,而是观察一定的时间,如果这段时间内这些非核心线程确实还是处于空闲状态,那么就会将这些非核心线程销毁掉~~~

KeepAliveTime --- 允许空间最大时间

unit ---- 时间单位(s,ns,ms,day,hour ....)

4.)任务队列

当有任务时,任务就分配到任务队列中,让线程池中的线程去取,当任务队列为空时,那么线程池中的线程就阻塞等地~~~

我们可以指定任务队列的容量capaci和数据结构(数组,链表,堆)

5.)线程工厂

线程工厂是为了解决构造方法的弊端

比如当我们如何确定一个点时,可以有俩种方法 1.)通过直角坐标系,由x轴和y轴确定一个点

2.)可以通过半径和角度确定一个点

我们写一个伪代码:

java 复制代码
public class Point {
//    通过x轴和y轴来确定一个点
    public Point(double x, double y) {
        //...

    }

//    通过半径和角度来确定一个点
    public Point(double r, doube a) {
        //...    
    }

}

这个代码是会编译报错的,因为这俩个构造方法无法构成重载,而构造方法的名字又需要固定~~~

即使变量名不同,但是有多种方法,但是无法通过构造方法去构建线程对象。

所以我们引进线程工厂,就是为了解决上述问题,参数是相同的,但是构造方法名称无法改变,无法通过多种方法去创建线程对象~~~

下面是线程工厂的伪代码:

java 复制代码
clas Point {
    public Point() {
    
    }


//    通过set方法来设置不同参数的方法创建Thread对象
    public void set() {

    }
}


//这个类就是Point的工厂类
class PointFactor {

//这样就可以通过不同名称的方法,但是参数类型个数又相同的方法来设置Thread对象~~~~

    public static Point BuildByXY(double x, double y) {
        Point p = new Point();
//通过set方法将x和y设置进去
        p.set();
        reutrn p;
    } 

    public static Point BuildByRA(double r, double a) {
        Point p = new Point();
//通过set方法将r和a设置进去
        p.set();
        return p;
    }

}

这种工厂模式,就可以很好的对线程池中大量的线程进行统一的设置管理,设置统一属性~~~

比如设置统一的名称

比如设置统一的优先级

比如设置统一的后台线程

......

6.)拒绝策略(四种)

1.)抛出异常

当任务队列满了的时候,如果继续往任务队列中添加任务,就抛出异常~~~

如果异常没有catch住,那程序就会崩溃

2.)让调用submit的线程自己执行

当某个线程调用submit方法,往任务队列中添加任务,但是由于任务队列满了,新的任务无法添加到任务队列,此时可以让调用submit的线程自己去执行这个新任务~~

3.)抛弃任务队列中最老的任务

当任务队满的时候,可以把任务队列中早加入的,但还未被执行的任务抛弃掉,给新的任务腾出空间,将这个位置留给新的任务~~~

4.)抛弃任务队列中最新的任务

当任务队列满的时候,将最慢被加入任务队列的,但还未被执行的任务抛弃掉,给新的任务腾出空间,将这个位置留给新的任务~~~

总而言之,在实际的开发过程中,我们认为新来的任务是比较重要的~~所以我们会想方设法让新来的任务加入到任务队列中

线程池的使用

因为标准库的线程池使用起来还是比较麻烦的,所以Java将线程池进一步进行封装!!

Java对线程池封装之后,也提供很多个版本,我们可以了解了解:

1.)有核心线程和非核心线程的线程池

2.)只有给定核心线程数,没有非核心线程的线程池

3.)只包含一个核心线程的线程池

4.)定时器线程池,当未来某个时间点执行

5.)只包含一个核心线程的定时器线程池

java 复制代码
public class Demo {
public static void main(String[] args) {

//创建只有5个核心线程的线程池,没有非核心线程
    ExecutorService executorService = Executors.newFixedThreadPool(5);


    for(int i = 1; i <= 1000; i++) {    

        int id = i;

//调用submit方法让线程池中的某个线程去执行任务
        executorService.submit(new Runnable() {
            public void run() {
                //执行任务
                String name = Thread.currentThread.getName();
                System.out.println(name + "拿到任务:" + id);
            }

        })
    }

 }
}

因为线程池内部的线程默认是前台线程,所以main函数结束了,线程池内的线程不会被销毁,直到整个线程池被销毁了~~~~

手动模拟实现线程池

java 复制代码
class MyThreadPool {


    //固定线程数的线程池

    //使用阻塞队列来入队列和出队列---- 当为空的时候就会阻塞
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

    public MyThreadPool(int n) {
//创建n个线程
        for(int i = 1; i <= n; i++) {
            Thread t = new Thread(() -> {
                try {
            //让线程不断地在while死循环中从任务队列获取任务,获取不到就阻塞等待任务到来
                    while(true) {
                        Runnable task = queue.take();
                        task.run();//执行任务
                    }
                }catch(InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            t.setDaemon(true);//设置为后台线程
            t.start();
        }
    }


    //提供submit方法
    public void submit(Runnable task) throws InterruptedException{
        queue.put(task);//往队列中添加任务
    }
}


public class Demo {
public static void main(String[] args) throws InterruptedException{
    MyThreadPool myThreadpool = new MyThreadPool(5);//给定5个核心线程

    for(int i = 1; i <= 1000; i++) {
        int id = i;
        myThreadPool.submit(() -> {
            String name = Thread.currentThread.getName();
            System.out.println(name + "拿到任务:" + id);
        });
    }

    Thread.sleep(1000);//因为线程池中的线程被我们设置为后台线程,所以要让main线程等待
 }
}
相关推荐
To Be Clean Coder2 小时前
【Spring源码】getBean源码实战(三)
java·mysql·spring
杰瑞不懂代码2 小时前
基于 MATLAB 的 BPSK/QPSK/2DPSK 在 AWGN 信道下的 BER 性能仿真与对比分析
开发语言·matlab·qpsk·2psk·2dpsk
Wokoo72 小时前
开发者AI大模型学习与接入指南
java·人工智能·学习·架构
小鸡脚来咯3 小时前
python虚拟环境
开发语言·python
龘龍龙3 小时前
Python基础(九)
android·开发语言·python
电摇小人3 小时前
我的“C++之旅”(博客之星主题作文)
java·开发语言
资生算法程序员_畅想家_剑魔3 小时前
Java常见技术分享-23-多线程安全-总结
java·开发语言
ytttr8733 小时前
MATLAB中CVX凸优化工具箱的使用指南
开发语言·matlab
萧曵 丶3 小时前
ArrayList 和 HashMap 自动扩容机制详解
java·开发语言·面试