<JavaEE> 经典设计模式之 -- 线程池

2023-12-13 14:30:22

目录

一、线程池的概念

二、Java 标准库中的线程池类

2.1 ThreadPoolExecutor 类

2.1.1?corePoolSize 和?maximumPoolSize

2.1.2?keepAliveTime 和?unit

2.1.3?workQueue

2.1.4?threadFactory

2.1.5?handler

2.1.6 创建一个参数自定义的线程池

2.2?Executors 类

2.3 实现自己的线程池


一、线程池的概念

1)什么是线程池?

准备预期需要使用的对象,将这些对象放入一个可以随时取用的容器中,这个容器就被称为“池”。

使用过的对象也不立刻销毁,而是放回“池”中,以备下次使用。

将线程作为上述对象,放在一个容器中,这就称为“线程池”。

2)为什么使用线程池?
在实际使用中,“线程池”并没有频繁的创建和销毁线程,而是从“池”中存取线程,这样可以减少每次启动和销毁线程的损耗,提高运行效率,减小系统开销。
3)为什么使用线程池可以提高效率?

通常创建线程,是通过向系统申请创建,需要通过系统内核完成,是具有内核态的代码。

而从线程池中存取线程,是属于用户态的代码,相比于内核态代码更可控、稳定,操作更快。


二、Java 标准库中的线程池类

2.1 ThreadPoolExecutor 类

这个类的构造方法参数众多,包含以下参数:

int corePoolSize

核心线程数

int maximumPoolSize

最大线程数

long keepAliveTime

空闲线程存活时间

TimeUnit unit

时间单位

BlockingQueue<Runnable> workQueue

任务队列

ThreadFactory threadFactory

线程工厂

RejectedExecutionHandler handler

拒绝策略

2.1.1?corePoolSize 和?maximumPoolSize

int corePoolSize?核心线程数

线程池中保持持有的最小线程数量。

int maximumPoolSize?最大线程数

线程池中可以持有的最大线程数量。
标准库提供的线程池,持有的线程数量是可以变动的,可以根据需要执行的任务数量,自适应线程的数量。

2.1.2?keepAliveTime 和?unit

long keepAliveTime?空闲线程存活时间

当线程池中的线程处于空闲状态时,会等待 keepAliveTime 时间后自动关闭。

如果keepAliveTime为0,线程在执行完任务后则不会关闭。

TimeUnit unit?时间单位
keepAliveTime?空闲线程存活时间的时间单位。

2.1.3?workQueue

BlockingQueue<Runnable> workQueue?任务队列

用于存储等待执行的任务。当线程池中的线程数量达到最大值时,新任务将被添加到队列中等待执行。

执行的任务应该是 Runnable 接口的实现类,

2.1.4?threadFactory

前置知识点:工厂模式

什么是工厂模式?

工厂模式(Factory Pattern)是一种创建型设计模式,它提供了一种在不指定具体类的情况下创建对象的接口。

工厂模式的核心思想是,将对象的创建过程抽象出来,使得使用者的创建与类的实例化过程解耦。

在工厂模式中,会实现一个工厂类,它提供一个创建对象的接口,而使用者只需要调用这个接口即可,工厂类会根据客户端的请求,返回不同类型的对象。

threadFactory?具体指什么:

ThreadFactory threadFactory?线程工厂
通过工厂类创建线程对象(简单理解就是通过这个类产出线程)。ThreadFactory 是一个接口,接口中只中有一个方法 newThread 方法,通过覆写该方法获得一个线程,同时可以给这个新线程设置一些属性。

2.1.5?handler

RejectedExecutionHandler handler?拒绝策略
线程池异常处理器,也称拒绝策略。这里的 handler 表示一个实现了 RejectedExecutionHandler 接口的实现类,传入的 handler 参数决定了出现异常时,线程池如何处理异常。
有哪几种拒绝策略,分别表示什么?
ThreadPoolExecutor.AbortPolicy当线程池已满,且阻塞队列已满,新任务无法执行,则立即抛出 RejectedExecutionException 异常,是默认策略。
ThreadPoolExecutor.CallerRunspolicy当线程池已满,且阻塞队列已满,新任务无法执行,则由调用线程自己执行新任务。
ThreadPoolExecutor.DiscardOldestPolicy当线程池已满,且阻塞队列已满,新任务无法执行,则丢弃最早添加的任务,然后添加新任务。
ThreadPoolExecutor.DiscardPolicy当线程池已满,且阻塞队列已满,新任务无法执行,则丢弃新任务,不执行。

2.1.6 创建一个参数自定义的线程池

创建并运行以下线程池和任务:

分析上述代码运行结果:

2.2?Executors 类

1)简单介绍 Executors 类

ThreadPoolExecutor 类参数较多,使用较复杂。因此,为方便使用,标准库中又提供了?Executors 工厂类。

Executors 类是对?ThreadPoolExecutor 类进行了一层封装,是一个工厂类,通过这个类创建出不同属性的线程池对象。
Executors 类返回的是一个?ExecutorService 类型的线程池实例。通过?ExecutorServic.submit 可以向线程池中添加任务。
2)简单介绍常用的 Executors 类创建线程池的方法

newFixedThreadPool

创建固定线程数的线程池

newCachedThreadPool

创建线程数目动态增长的线程池

newSingleThreadExecutor

创建只包含单个线程的线程池

newScheduledThreadPool

创建可以定时执行任务的线程池
3)开发过程中应该使用?ThreadPoolExecutor 类还是使用 Executors 类?

ThreadPoolExecutor 类参数较多、可以自定义,这意味着使用这个类创建的线程池更灵活。

而?Executors 类相比 ThreadPoolExecutor 类多了一层封装,部分参数已经设定好,这使得?Executors 类在使用上更便利。

这两种选择,前者更偏向于高度定制化的线程池,而后者偏向于通用性。两者并无高下之分,各有偏向,使用者根据实际应用场景使用即可。

2.3 实现自己的线程池

线程池的使用有以下关键点:

<1>

线程池,肯定要有线程。在这里实现一个线程数量固定的线程池,就需要在这个类的构造方法中,根据输入的参数 n ,新建 n 个线程。
<2>维护一个阻塞队列,队列持有需要执行的任务。
<3>提供一个 submit 方法,用于将任务添加到任务队列中。
<4>线程池中的线程,也有自己的任务,就是不断地扫描任务队列,如果队列中有任务,则取出任务后执行。
<5>注意新建的线程需要有合适的启动时机。在以下实现中,我们让线程一创建就启动。

代码演示线程池的实现:

class MyThreadPool{
    //创建一个用于存放任务的队列;
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(5);

    //构造方法,用于构造线程池。方法中需要将线程new出来;
    public MyThreadPool(int n){
        //设置线程需要执行的任务;
        Runnable runnable = new  Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        //从任务队列中取出任务,并执行;
                        queue.take().run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        //循环创建线程,并启动,将线程放入队列中;
        for (int i = 0; i < n; i++){
            Thread t = new Thread(runnable);
            t.start();
        }
    }

    //添加任务方法。往任务队列中添加任务;
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}
public class ThreadPool_Demo35 {
    public static void main(String[] args) throws InterruptedException {
        //新建线程池;
        MyThreadPool threadPool = new MyThreadPool(2);

        //给线程池添加任务;
        for (int i= 0; i < 10; i++){
            int n = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " 执行任务:" + n);
                }
            });
        }
    }
}


//运行结果:
Thread-0 执行任务:0
Thread-1 执行任务:1
Thread-0 执行任务:2
Thread-1 执行任务:3
Thread-0 执行任务:4
Thread-0 执行任务:6
Thread-0 执行任务:7
Thread-0 执行任务:8
Thread-0 执行任务:9
Thread-1 执行任务:5
...

十个任务全部成功打印。
应该注意到,线程没有执行结束,还在等待新的任务加入,这是线程池的合理功能。

阅读指针 -> 《 Synchronized 锁进阶 -- 锁策略》

<JavaEE> 锁进阶 -- 锁策略(乐观锁和悲观锁、重量级锁和轻量级锁、自旋锁和挂起等待锁、可重入锁和不可重入锁、公平锁和非公平锁、读写锁)-CSDN博客介绍了以下锁策略:乐观锁和悲观锁、重量级锁和轻量级锁、自旋锁和挂起等待锁、可重入锁和不可重入锁、公平锁和非公平锁、读写锁;https://blog.csdn.net/zzy734437202/article/details/134916407

文章来源:https://blog.csdn.net/zzy734437202/article/details/134860513
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。