Java【多线程】(5)线程池

发布于:2025-03-28 ⋅ 阅读:(27) ⋅ 点赞:(0)


目录

1.前言

2.正文

2.1线程池引入

2.2标准库中的线程池

2.3手搓线程池

3.小结


1.前言

哈喽大家好吖,今天来给大家继续进行多线程——线程池方面的学习,线程池在以后项目中也是关键的存在,废话不多说让我们开始吧。

2.正文

2.1线程池引入

线程池,就是为了让我们高效的创建销毁线程的,最初引入线程的原因:频繁创建销毁进程,太慢了。随着互联网的发展,随着我们对于性能要求更进一步。咱们现在觉得,频繁创建销毁线程,开销有些不能接受了~~

解决策略有哪些呢,一个使用线程池(里面有现成的线程,随取随用),还可以使用协程(类似线程之于进程,协程之于线程)。这里我们就讲解和多线程有关的线程池啦。

什么是线程池呢?线程池是一种 线程管理机制,用于 复用线程、控制并发数量、提高系统性能。它通过预先创建一定数量的线程,并将任务提交给这些线程执行,避免了频繁创建和销毁线程的开销。

我们之前多线程编程中,由于应用场景较为简单,所以有些问题不会很明显,但有些性能提升和潜在的问题都可以由线程池解决:

  • 线程创建和销毁开销大:每次 new Thread() 都需要系统资源,线程销毁后资源无法复用。

  • 线程数量不可控:如果每个任务都创建一个线程,可能导致系统资源耗尽。

  • 缺乏统一管理:线程的生命周期难以控制,容易造成资源泄漏或死锁。

使用线程池可以达到什么效果呢?

✅ 降低资源消耗:复用已创建的线程,减少线程创建/销毁的开销。
✅ 提高响应速度:任务到达时,无需等待线程创建即可执行。
✅ 控制并发数量:防止线程过多导致系统崩溃。
✅ 提供任务管理:支持任务队列、拒绝策略等机制。 

2.2标准库中的线程池

我们主要讲解一个最经典常见的库线程池(下面是官方文档):

ThreadPoolExecutor (Java SE 17 & JDK 17)https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html

我们看线程池中的构造方法,参数这么多,而且基本都是没有见过的名词,下文详解:

  • int corePoolSize:核心线程数,即至少多少个线程池,线程池一旦创建,核心线程随之创建,线程池销毁随之销毁。
  • int maximunPoolSize:最大线程数,即非核心线程数,销毁与创建自适应。
  •  long keepAliveTime:非核心线程的最大空闲时间。
  • TimeUnit unit:keepAliveTime的时间单位。
  • Blocking0ueue<Runnable> workOueue:这就是之前博文讲解的阻塞队列了,因为线程池本质工作原理也是生产消费者模型。

还有两个属性,是线程池的核心属性,需要详细讲解:

ThreadFactory threadFactory(线程工厂):

线程工厂(Thread Factory)是一种用于创建线程的机制,它允许用户自定义线程的创建过程,而不是直接使用默认的线程创建方式。在Java等编程语言中,线程工厂通常用于线程池(Thread Pool)中,用于统一管理线程的创建和配置。


介绍工厂模式:

工厂模式是一种创建型设计模式,用于将对象的创建与使用分离,提升代码的灵活性和可维护性。

工厂方法(即makePointByXY)的核心,通过静态方法,把构造对象 new 的过程各种属性初始化的过程,封装起来了。提供多组静态方法,实现不同情况的构造。

提供工厂方法的类叫做工厂类

下面是一个简单的工厂模式示例:

class Point {

}

class PointFactory {
    public static Point makePointByXY(double x, double y) {
        Point p = new Point();
        // 通过 x 和 y 给 p 进行属性设置
        return p;
    }

    public static Point makePointByRA(double r, double a) {
        Point p = new Point();
        // 通过 r 和 a 给 p 进行属性设置
        return p;
    }
}

public class test {
    public static void main(String[] args) {
        Point p = PointFactory.makePointByXY(10, 20);
    }
}

RejectedExecutionHandler handler(拒绝策略):

线程池的拒绝策略(Rejected Execution Policy)是当线程池无法接受新任务时采取的处理方式。当线程池的任务队列已满且所有工作线程都在忙碌时,新提交的任务就会触发拒绝策略。


拒绝策略会在以下情况下触发:

  1. 线程池已关闭(调用了 shutdown() 或 shutdownNow()

  2. 线程池已满

    • 工作线程数已达到 maximumPoolSize

    • 任务队列已满(如果使用的是有界队列)


Java 提供了 4 种内置拒绝策略,均实现了 RejectedExecutionHandler 接口:

策略 行为 适用场景
AbortPolicy(默认) 直接抛出 RejectedExecutionException 需要严格保证任务不丢失
CallerRunsPolicy 由提交任务的线程自己执行 避免任务丢失,但可能阻塞调用线程
DiscardPolicy 直接丢弃新任务,不报错 允许任务丢失的场景
DiscardOldestPolicy 丢弃队列中最旧的任务,然后重新提交 允许丢弃旧任务

如果在创建线程池的时候,填写这么多参数也会有些麻烦,也有更简单的版本,代码示例:

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

public class demo6 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 0;i < 100;i++){
            int id = i;
            threadPool.submit(()->{
                System.out.println("第" + id + "个任务被" + Thread.currentThread().getName() + "解决");
            });
        }
        threadPool.shutdown();
    }
}

2.3手搓线程池

手搓线程池的话就需要考虑到自己实现一些关键的任务阻塞队列以及submit方法,详解如下:

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

class myThreadPool{
    private BlockingQueue<Runnable> tasks = null;

    public myThreadPool(int n){
        tasks = new ArrayBlockingQueue<>(1000);

        for(int i = 0;i < n;i++){
            Thread thread = new Thread(()->{
                try {
                    while(true){
                        Runnable task = tasks.take();
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }});
            thread.start();
        }
    }
    public void submit(Runnable task)throws InterruptedException{
        tasks.put(task);
    }

}


public class demo7 {
    public static void main(String[] args) {
        myThreadPool pool = new myThreadPool(10);

        for (int i = 0; i < 100; i++) {
            int id = i;
            try {
                pool.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + "id=" + id);
                });
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

核心组件

  1. 任务队列:使用BlockingQueue来存储待执行的任务

    • ArrayBlockingQueue是一个有界阻塞队列

    • 容量设置为1000,即最多可存放1000个待执行任务

  2. 工作线程:在构造函数中创建多个线程作为"工人"


工作流程

  1. 初始化阶段

    • 创建指定数量(n)的工作线程

    • 每个线程启动后立即进入等待任务状态

  2. 任务执行流程

    • 每个工作线程不断循环,从队列中取出任务(tasks.take())

    • 取到任务后执行任务的run()方法

    • 执行完继续取下一个任务

  3. 任务提交

    • 外部通过submit()方法提交任务

    • 任务被放入任务队列(tasks.put(task))

    • 工作线程会自动从队列中获取并执行


关键特性

  1. 阻塞队列的作用

    • 当队列为空时,take()会使线程阻塞等待

    • 当队列满时,put()会使提交任务的线程阻塞等待

  2. 线程复用

    • 线程创建后不会销毁,而是反复从队列中取任务执行

    • 避免了频繁创建销毁线程的开销

  3. 简单但完整

    • 实现了线程池的基本功能

    • 缺少高级功能如线程数动态调整、拒绝策略等

3.小结

今天的分享到这里就结束了,喜欢的小伙伴点点赞点点关注,你的支持就是对我最大的鼓励,大家加油!