当前位置: 首页 > news >正文

【多线程】线程池

目录

一.池的概念

二.线程池的概念

三.标准库中的线程池

四.模拟线程池


一.池的概念

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

  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方法添加任务

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()方法将任务存放进入队列中
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! 

相关文章:

  • 【开发问题记录】Docker Hub 执行 docker pull命令,拉取镜像失败/sudo docker run hello-world报错
  • 爬虫获取1688关键字搜索接口的实战指南
  • PyTorch量化进阶教程:第二章 Transformer 理论详解
  • [GESP202503 C++六级题解]:P11962:树上漫步
  • Docker学习--容器生命周期管理相关命令--docker rm 命令
  • 【word】导出批注具体到某段引用
  • 【一起来学kubernetes】31、Helm使用详解
  • Redis 02
  • 深入C++栈:从STL到底层实现的全面解析
  • TCP 三次握手与四次挥手深度解析(面试高频)
  • 百度热力图数据获取,原理,处理及论文应用25
  • SEO长尾关键词优化实战策略
  • webpack和vite的区别是什么
  • NAT穿越
  • Ollama+open-webui搭建私有本地大模型详细教程
  • HashMap 在 JDK 1.7 和 JDK 1.8 有什么区别
  • EasyExcel导出自动回显中文,读取自动转换码值(基于全局转换器与自定义注解)
  • 基于SpringBoot的高校学术交流平台
  • FPGA学习(三)——数码管实现四位分秒计数器
  • 【Easylive】saveCategory方法中的if判断(对应增加和修改)
  • 特朗普与普京开始进行电话会谈,稍后还将致电泽连斯基
  • 南宁海关辟谣网传“查获600公斤稀土材料”:实为焊锡膏
  • “马上涨价”再到“吞下关税”,美政策让沃尔玛“输两次”
  • 缅甸发生5.0级地震
  • 高飞已任南航集团党组副书记
  • 工人日报:应对“职场肥胖”,健康与减重同受关注