【多线程】线程池

目录

一.池的概念

二.线程池的概念

三.标准库中的线程池

四.模拟线程池


一.池的概念

是一种资源复用和管理技术,预先创建并维护一组可以重复利用的资源,减少重复创建和销毁的开销,达到节省资源的目的

  1. 资源复用:重复利用已分配的资源,减少重复创建和销毁的开销
  2. 资源管控:限制资源总量(如果资源太多,容易发生线程安全问题)

二.线程池的概念

在最开始使用进程的时候,由于进程 的创建和销毁开销太大,于是引入了线程的概念(进程的更精细划分),虽然线程的开销很小,但是在频繁的创建和释放情况下,线程的开销也不得不重视起来,针对这个问题进行了优化。

  • 协程 / 纤程:轻量化线程,对线程的更精细划分

一个代码中,创建出上千个线程,可能会卡死,如果是上千个协程,只能说还不够

  • 线程池

将要使用的线程创建好,使用完后,将线程放入线程池中,下一次使用的时候,直接从线程池中取,不需要创建和销毁

并且在池子中获取线程,属于纯用户态操作(可控),由系统来创建,属于内核态操作(不可控),所以从线程池中获取线程 比从系统中申请线程更高效


三.标准库中的线程池

在java中,线程池的实现基于 java.util.concurrent 包中的 ThreadPoolExecutor

ThreadPoolExecutor类

maximumPoolSize (最大线程数)=核心线程数+空闲线程数

BlockingQueue<Runnable> workQueue (工作队列):这里使用Runnable来描述任务,我们也可以改进,使用PriorityBlockingQueue,带有优先级的阻塞队列,控制任务的执行顺序

ThreadFactory threadFactory (线程工程):线程池提供定制化线程的能力

拒绝策略!!!

线程池中,有一个阻塞队列,能够容纳的元素有上限,如果队列满了,还要继续添加任务,线程池会有四种解决办法

策略 行为 适用场景
AbortPolicy(默认策略) 抛异常 关键任务,需明确失败反馈
CallerRunsPolicy 提交者线程执行任务 缓解临时负载压力
DiscardPolicy 静默丢弃 非关键任务(如日志)
DiscardOldestPolicy 丢弃队列最旧任务并重试提交 实时性优先

AbortPolicy(默认策略)

  • 抛出异常,导致新任务和旧任务都不执行
  • 需要手动处理异常

CallerRunsPolicy

  • 新的任务由添加任务的线程去执行(新的任务会执行,不是由线程池中的线程去执行,而是由调用者执行)
  • 减缓任务提交速度,避免线程池过载,但是可能导致阻塞提交任务的线程。

DiscardPolicy

  • 丢弃最新的任务(新的任务直接销毁了,调用的线程和线程池都不执行)
  • 无异常处理负担,但是任务丢失不易察觉。

DiscardOldestPolicy

  • 丢弃最老的任务,去执行新的任务
  • 可能丢弃重要旧任务。

在我们的日常使用中,由于ThreadPoolExecutor类使用起来太复杂,标准库中提供了另一个Executors工厂类对ThreadPoolExecutor类进行封装并且设计好了不同的参数

下面是常用的方法:

|--------------------------------------|----------------|
| 方法 | 说明 |
| newScheduleThreadExecutor() | 创建定时器线程,延时执行任务 |
| newSingleThreadExecutor() | 只包含单个线程的线程池 |
| newCachedThreadExecutor() | 线程数目能够动态扩容 |
| newFixedThreadExecutor(int nThreads) | 创建固定大小的线程池 |

无论是ThreadPoolExecutor类还是Executor类都是使用submit方法添加任务

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class Demo_7 {
    public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(3);
        service.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("111");
            }
        });
    }
}

在创建线程池的时候,会牵扯一个问题:线程池中线程的数量设置多少合适?

没有固定的答案,不同的程序,能够设定的线程数量是不同的

  1. 如果在一个进程中,所有的线程都是cpu密集型,那么每个线程的工作都在cpu上面,那么建议线程数目不要超过N(cpu核心数)
  2. 如果在一个进程中,所有的线程都是IO密集型,那么每个线程的工作都在等待IO,那么建议线程数目远远超过超过N(cpu核心数)

由于在实际开发中,一部分是cpu,另一部分IO,最好的办法是根据试验和测试找出合适的线程数

在不同的线程数目下,参考在总的时间的开销和系统资源的开销中,找到合适的量

四.模拟线程池

实现一个线程池,我们要明确以下步骤:

  1. 为了保证创建线程池就可以执行任务,将线程创建在构造方法中(只要创建出实例就会启动线程,执行任务)
  2. 构造方法中指定线程池中使用多少个线程
  3. 使用阻塞队列创建任务队列,当队列中没有任务的时候,可以实现阻塞的功能
  4. 提供一个submit方法,可以将任务添加进入任务队列中

主要步骤:

  1. 在构造方法中,使用for循环创建出指定的线程数,每个线程都进入while死循环,一直执行任务,如果队列中没有任务,则会进入take()方法**(带有阻塞功能)**
  2. 使用submit()方法将任务存放进入队列中
java 复制代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class MyThreadPoolExecutor{
//    存放线程
    List<Thread> list = new ArrayList<>();
//    存放任务
    BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
    MyThreadPoolExecutor(int n){
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(()->{
                //线程具体的工作
                while(true){
                    try {
                        //取出任务
                        //使用take方法,可以进入阻塞
                        Runnable x =  queue.take();
                        //执行任务
                        x.run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            //将每一个线程都启动起来
            t.start();
            list.add(t);
        }
    }

    //添加任务
    public void submit(Runnable runnable) throws InterruptedException {
        //会进行唤醒操作
        queue.put(runnable);
    }
}
public class Demo_8 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPoolExecutor executor = new MyThreadPoolExecutor(9);

        for (int i = 0; i < 1000; i++) {
            //此处i是变量不能打印出,需要变成常量
            int n = i;
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程名:"+Thread.currentThread().getName()+"打印:"+n);
                }
            });
        }
    }
}

点赞的宝子今晚自动触发「躺赢锦鲤」buff!

相关推荐
邪恶的贝利亚4 分钟前
定时器设计
java·linux·前端
工业互联网专业5 分钟前
基于springboot+vue的机场乘客服务系统
java·vue.js·spring boot·毕业设计·源码·课程设计·机场乘客服务系统
饕餮争锋6 分钟前
WebMvcConfigurer介绍-笔记
java·笔记·servlet
招风的黑耳12 分钟前
Java集合框架详解与使用场景示例
java·开发语言
xrkhy14 分钟前
java中XML的使用
xml·java·开发语言
抽风的雨61017 分钟前
【python基础知识】Day 27 函数专题2:装饰器
开发语言·python
胡斌附体24 分钟前
idea挂掉,会导致进程不结束,切换profile环境,导致token认证不通过
java·ide·intellij-idea·调试·token失效
martian6651 小时前
医学影像系统性能优化与调试技术:深度剖析与实践指南
开发语言·系统安全·dicom
y102121041 小时前
Pyhton训练营打卡Day27
java·开发语言·数据结构
AA-代码批发V哥1 小时前
Java类一文分解:JavaBean,工具类,测试类的深度剖析
java·开发语言