【多线程】线程池(上)

文章目录

  • 线程池基本概念
    • 线程池的优点
    • 线程池的特点
  • 创建线程池
    • 自定义线程池
    • 线程池的工作原理
    • 线程池源码分析
    • 内置线程池
      • `newFixedThreadPool`
      • `SingleThreadExecutor`
      • `newCachedThreadPool`
      • `ScheduledThreadPool`
  • 线程池的核心线程是否会被回收?
  • 拒绝策略
    • ThreadPoolExecutor.AbortPolicy
    • ThreadPoolExecutor.CallerRunsPolicy
    • ThreadPoolEcecutor.DiscardPolicy
    • ThreadPoolExecutor.DiscardOldestPolicy

线程池基本概念

在讲解线程池之前,我们应该先来了解池化技术

池化技术:
池化技术是一种用于管理和复用线程的技术,旨在提高系统的性能和资源利用率。线程池通过预先创建一组线程来处理任务,从而避免频繁地创建和销毁线程带来的开销

池化技术不仅在线程池中体现,还在数据库连接池,HTTP连接池,字符串常量池中均有体现.

如果不使用池化技术,我们创建一个线程的步骤:

  • 手动创建线程对象
  • 执行任务
  • 执行完毕,释放线程对象
    此处,每一次创建线程资源和释放线程资源,均会消耗系统资源,而如果我们采用线程池的方式,由线程池统一创建和管理线程资源,就可以降低系统资源的消耗,提高对资源的利用率.

所以线程池,提供一种限制和管理资源的方式.

线程池的优点

  • 降低资源消耗:通过重复利用已经创建的线程降低线程创建和销毁造成的资源消耗.
  • 提高程序的响应速度:当任务到达时,任务可以不需要等待线程的创建就可以立即执行
  • 便于统一管理线程对象:线程池可以统一进行线程分配
  • 可以控制最大的并发数(线程池会限制最大的线程对象的个数,这样可以限制并发量,避免无限制的创建线程资源,造成系统资源的损耗)

线程池的特点

  • 线程复用
  • 控制最大并发数
  • 管理线程

创建线程池

自定义线程池

对于线程池的管理和使用,我们使用在java.util.concurrent下的ThreadPoolExecutor线程池工具类
来进行线程池的创建管理

public class ThreadPoolExecutor extends AbstractExecutorService{
    //成员属性:
    private volatile int corePoolSize;//核心线程数
    private volatile int maximumPoolSize;//最大线程数
    private volatile long keepAliveTime;//非核心线程的最大存活时间
    private final BlockingQueue<Runnable> workQueue;//工作队列
    private volatile ThreadFactory threadFactory;//线程工厂
    private volatile RejectedExecutionHandler handler;//拒绝策略
   //...
    
    //线程池的构造方法:
    public ThreadPoolExecutor(int corePoolSize, 
                              int maximumPoolSize, 
                              long keepAliveTime, 
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler
                              )
   
   //...                           
  }       

参数:

  • corePoolSize:任务队列未达到队列最大容量时,最大可以同时运行的线程数量
  • maximumPoolSize:任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数
  • workQueue:新任务来的时候,会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中
  • keepAliveTime:当线程池中的线程数量大于corePoolSize,即有非核心线程(线程池中核心线程以外的线程)时,这些非核心线程空闲后不会立即被销毁,而是会等待,直到等待的时间超过了keepAliveTime才会被回收销毁
  • Unit:keepAliveTime参数的时间单位
  • threadFactory:executor创建新线程时会用到
  • headler:拒绝策略(后续详解))

接下来我们采用图解的方式,来系统讲解一下这里各种参数具体使用,并了解线程池的工作原理.

线程池的工作原理

我们下述线程池原理的讲解,以一个corePoolSize(核心线程数)为9,maximumPoolSize(最大线程数)为13,workQueue(工作队列)大小为5,keepAliveTime(非核心线程存活时间)为10s来举例.

  • 当此时的线程池中已被创建的线程<核心线程数,线程池会创建新的线程执行任务

  • 此时的线程池中已创建的线程池数==核心线程数
    在这里插入图片描述

  • 此时新的任务进入线程池中,会先进入阻塞队列中进行等待,核心线程中如果存在空闲的线程,就会去阻塞队列中取任务进行执行
    在这里插入图片描述
    在这里插入图片描述

  • 如果工作阻塞队列已满,核心线程也在执行任务,但最大线程数>已创建线程数,那么进入的任务后,线程池会创建新的线程,直到最大线程数==已创建线程数.
    在这里插入图片描述

  • 在线程池中工作阻塞队列已满,且已创建线程数==最大线程数时,此时再来任务时,线程池就会触发拒绝策略.
    在这里插入图片描述

  • 当线程池中的额外创建的线程空闲下来了,是否马上就会销毁呢?
    并不是,线程池会根据设定的keepAliveTime时间来判断什么时候销毁这些线程.
    那么是不是一定要销毁原本创建的额外创建的线程呢?
    并不是,线程池只需要维护核心线程数最大线程数的数量即可,所以线程池中的线程并没有一个明确的身份
    在这里插入图片描述
    其中的几个线程空闲时间超过了keepAliveTime时间后:
    在这里插入图片描述
    以上就是线程池的工作原理和参数的理解.

    线程池源码分析

    我们从execute入手,了解线程池的源码中是如何实现上述过程的.

    public void execute(Runnable command) {
         if (command == null)
             throw new NullPointerException();
                 int c = ctl.get();
         if (workerCountOf(c) < corePoolSize) {
             if (addWorker(command, true))
                 return;
             c = ctl.get();
         }
         if (isRunning(c) && workQueue.offer(command)) {
             int recheck = ctl.get();
             if (! isRunning(recheck) && remove(command))
                 reject(command);
             else if (workerCountOf(recheck) == 0)
                 addWorker(null, false);
         }
         else if (!addWorker(command, false))
             reject(command);
     }
    

    在这里插入图片描述
    接下来我们来观察一下addWorker方法里是如何创建一个新的线程的

private boolean addWorker(Runnable firstTask, boolean core) {
//......
 Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;

在这里插入图片描述
我们发现,最终是调用了ThreadFactory参数来创建线程的.

这里还需要注意Worker类是实现了Runnable接口的类,所以Worker本身也是一个Runnable,所以创建线程是,调用的this.thread=getThreadFactory().newThread(this)传入的this对象就是Worker,所以线程执行的run()方法,是Worker内部的run()方法,而调用内部的run()方法后,在调用runWorker(this)方法,我们来看一看runWorker()方法中的内容:
在这里插入图片描述
我们看到,这里传入了参数this对象,其中this中的成员属性firstTask就是我们传入的FirstTask
在这里插入图片描述
在这里我们看到了task.run()的身影,说明此处执行了我们传入的任务,并且在执行任务之前和执行任务之后还可以调用相关的方法

接下来我们来观察一下是如何将任务加入到队列中的.
在这里插入图片描述
底层的工作队列是阻塞队列,任务会通过offer方法加入到队列中,此时如果队列中没有任务,线程来队列中获取任务时,就会阻塞在这个位置,直到有任务加入到队列中.所以使用阻塞队列,也实现了核心线程的保活

内置线程池

除了使用JUC包下的ThreadPoolExecutor来创建线程,我们还可以使用JUC包下的工具类Executor创建具有一定固定功能的线程池
建立一个任务(后面的几个实验也是使用此任务执行)

      class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"执行任务");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

newFixedThreadPool

newFixedThreadPool:创建固定线程数量的线程池

使用newFixedThreadPool创建一个线程数为4的线程池

public class test1 {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService threadPool= Executors.newFixedThreadPool(4);
        for(int i=0;i<10;i++){
            threadPool.execute(new MyRunnable());
        }
        TimeUnit.SECONDS.sleep(15);

        threadPool.shutdown();

        System.out.println(threadPool.isShutdown());
    }
}

运行结果:
在这里插入图片描述
我们发现,这里会重复利用4个线程,不会创建多于4的线程.

SingleThreadExecutor

SingleThreadExecutor:只有一个线程的线程池

public class test2 {
    public static void main(String[] args) throws InterruptedException {

        ExecutorService threadPool= Executors.newSingleThreadExecutor();
        for(int i=0;i<10;i++){
            threadPool.execute(new MyRunnable());
        }
        TimeUnit.SECONDS.sleep(15);
        threadPool.shutdown();

    }
}

运行结果:
在这里插入图片描述

newCachedThreadPool

newCachedThreadPool:可以根据实际情况动态调整线程数量的线程池

public class test3 {
    public static void main(String[] args) {
        ExecutorService threadPool= Executors.newCachedThreadPool();
        for(int i =0;i<10;i++){
            threadPool.execute(new MyRunnable());
        }

        threadPool.shutdown();
    }
}

运行结果:
在这里插入图片描述
我们发现,此时线程池选择创建十个线程来执行这十个任务

ScheduledThreadPool

ScheduledThreadPool:给定的延迟后运行任务或者定期执行任务的线程池

线程池的核心线程是否会被回收?

ThreadPoolExecutor默认不会回收核心线程,即使他们已经空闲了.这是为了减少创建线程的开销,因为核心线程通常是要长期保持活跃的.
但如果线程池是用于周期性使用的场景(也就是线程池忙碌-空闲存在周期性),可以使用allowCoreThreadTimeOut(boolean value)方法的参数设置为true,这样就会回收空闲的核心线程

拒绝策略

我们在阅读源码的过程中,已经关注到了拒绝策略这个问题.也就是说:当线程池已经达到最大并发量的时候,我们需要对新来的任务进行"拒绝".那么线程池中的拒绝策略有哪些呢?

ThreadPoolExecutor.AbortPolicy

抛出RejectedExecutionException直接拒绝新任务的处理(这里线程池默认的拒绝策略)

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

        BlockingQueue workQueue=new ArrayBlockingQueue(1);

        ExecutorService threadPool= new ThreadPoolExecutor(1,2,1L,TimeUnit.SECONDS,workQueue);

        for(int i=0;i<10;i++){
            threadPool.execute(new MyRunnable());
        }
        threadPool.shutdown();

    }
}

分析代码:
此处的场景中,线程池的最大并发量是3,但我们需要执行十个任务,那么一定会触发默认的拒绝策略.
运行结果:
在这里插入图片描述
我们看到,这里执行了三个任务,就在主线程中抛出了RejectedExecutionException

ThreadPoolExecutor.CallerRunsPolicy

这个是使用调用execute方法的线程来运行被拒绝的任务,如果执行execute方法的线程已关闭,则会丢弃该任务.

public static void main(String[] args) {                                                                                                                                             
                                                                                                                                                                                     
    BlockingQueue workQueue=new ArrayBlockingQueue(1);                                                                                                                               
                                                                                                                                                                                     
    ThreadPoolExecutor threadPool= new ThreadPoolExecutor(1,2,1L,TimeUnit.SECONDS,workQueue,new ThreadPoolExecutor.CallerRunsPolicy());                                              
                                                                                                                                                                                     
    for(int i=0;i<10;i++){                                                                                                                                                           
        threadPool.execute(new MyRunnable());                                                                                                                                        
    }                                                                                                                                                                                
    threadPool.shutdown();                                                                                                                                                           
                                                                                                                                                                                     
}                                                                                                                                                                                    
}                                                                                                                                                                                    

分析代码:
这里我们创建了一个最大并发数为3的线程池,并且将拒绝策略设定为ThreadPoolExecutor.CallerRunsPolicy,此时我们在运行过程中,肯定会存在线程达到了最大并发数,触发拒绝策略的情况.而ThreadPoolExecutor.CallerRunsPolicy会将需要执行的任务退回给调用者(即调用execute方法的线程,在此处是main线程),由调用者线程来执行该任务,这样就能够保证所有任务都不会被抛弃,而是被执行

运行结果:
在这里插入图片描述
我们看到这里mian方法执行了任务

ThreadPoolEcecutor.DiscardPolicy

不处理新任务,直接丢掉

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

        BlockingQueue workQueue=new ArrayBlockingQueue(1);

        ThreadPoolExecutor threadPool= new ThreadPoolExecutor(1,2,1L, TimeUnit.SECONDS,workQueue,new ThreadPoolExecutor.DiscardPolicy());

        for(int i=0;i<10;i++){
            threadPool.execute(new MyRunnable());
        }
        threadPool.shutdown();

    }
}

运行结果:
在这里插入图片描述
这里我们发现,线程池只执行了三个任务,就结束了,并没有抛出异常或者将任务退回给execute的调用者.

ThreadPoolExecutor.DiscardOldestPolicy

此策略将丢弃掉阻塞队列中最早的任务.
我们来进行一个实验:

public class test7 {
    private  static int i=1;
    static class MyRunnable_1 implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"执行任务1");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    static class MyRunnable_2 implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"执行任务2");
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    static class MyRunnable_3 implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"执行任务3");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue workQueue=new ArrayBlockingQueue(1);

        ThreadPoolExecutor threadPool= new ThreadPoolExecutor(1,1,1L, TimeUnit.SECONDS,workQueue,new ThreadPoolExecutor.DiscardOldestPolicy());

            threadPool.execute(new MyRunnable_1());
            threadPool.execute(new MyRunnable_2());
            threadPool.execute(new MyRunnable_3());

        threadPool.shutdown();
    }
}

分析代码:
这里我们创建了三个任务,分别是MyRunnable_1,MyRunnable_2,MyRunnable_3,我们设定线程池的最大并发数为2,即最多线程数为1,阻塞队列为1,这样三个任务执行的时候,首先会执行第一个任务,而第一个任务需要10s,第二个任务进入阻塞队列中进行等待,第三个任务进入触发拒绝策略,所以会将第二个任务抛弃掉.所以代码最终呈现的结果应该是:任务1,3被执行,任务2被抛弃
运行结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/891250.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

撸猫变梳毛?怎么解决猫咪掉毛问题?好用的宠物空气净化器推荐

秋风一吹&#xff0c;新一轮的猫咪换毛季又到了&#xff0c;这也意味着我失去了撸猫自由。我每天的治愈方式就是下班撸猫&#xff0c;抚摸着柔软的毛发&#xff0c;好像一天的烦恼都消除了。可是一到换毛季&#xff0c;猫还没撸两下&#xff0c;先从猫咪身上带下一手毛&#xf…

ASP.NET Core8.0学习笔记(二十一)——EFCore关系配置API

一、关系配置API概述 当我们需要指定一个字段作为外键&#xff0c;而这个外键又不符合以上四种约定时&#xff0c;就需要在IEntityTypeConfiguration实现类&#xff08;对应的配置类&#xff09;中使用Fluent API直接配置外键。理论上可以通过API直接指定一个属性&#xff0c;…

关于Qt音乐播放器进度条拖拽无用的问题解决方案

在使用Qt编写音乐播放器的时候&#xff0c;进度条关联播放音乐基本是必须的。那么在设计的过程中你可能会碰到一个奇怪的问题就是拖拽进度条的时候&#xff0c;可能会报错如下&#xff1a; 然后音乐就卡着不动了。。。 connect(ui->volume_toolButton,&VolumeToolBtn::…

LLM - 配置 ModelScope SWIFT 测试 Qwen2-VL 视频微调(LoRA) 教程(3)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/142882496 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 SWIFT …

挖掘空间数据要素典型领域应用场景

空间数据要素作为数字经济的基石&#xff0c;正在多个领域发挥着重要作用。随着技术的发展&#xff0c;空间数据的应用场景不断拓展&#xff0c;为各行各业带来了深刻的变革。以下是几个典型的空间数据要素应用领域&#xff1a; 1. 城市规划与管理 空间数据在城市规划和管理中…

在线培训知识库+帮助中心:教育行业智慧学习的创新桥梁

在数字化转型的浪潮中&#xff0c;教育行业正经历着前所未有的变革。为了应对日益增长的学习需求&#xff0c;提升教育质量&#xff0c;构建一个集在线培训知识库与帮助中心于一体的智慧学习环境&#xff0c;已成为教育行业转型升级的重要方向。这一创新模式不仅优化了学习资源…

雷池社区版配置遇到问题不要慌,查看本文解决

很多新人不太熟悉反向代理&#xff0c;所以导致配置站点出现问题 配置问题 记录常见的配置问题 配置后攻击测试没有拦截记录 检查访问请求有没有真实经过雷池 有很多新人配置站点后&#xff0c;真实的网站流量还是走的源站&#xff0c;导致雷池这边什么数据都没有 配置后…

【CTF Web】Pikachu 不安全的url跳转 Writeup(URL重定向+代码审计)

不安全的url跳转 不安全的url跳转问题可能发生在一切执行了url地址跳转的地方。 如果后端采用了前端传进来的(可能是用户传参,或者之前预埋在前端页面的url地址)参数作为了跳转的目的地,而又没有做判断的话 就可能发生"跳错对象"的问题。 url跳转比较直接的危害是:…

springboot 整合 rabbitMQ(2)

springboot 整合 rabbitMQ&#xff08;1&#xff09;-CSDN博客 上期说了rabbitMQ的基础用法&#xff08;普通队列模式&#xff09; 这期学习一下如何防止消息重复消费和进阶用法&#xff08;订阅者模式&#xff09; 目录 重复消费问题 导致 RabbitMQ 重复消费问题的原因&a…

中安未来 OCR:开启高效身份证件识别新时代

在数字化快速发展的今天&#xff0c;高效准确地处理各类信息变得至关重要。中安未来 OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;技术以其卓越的性能和广泛的应用场景&#xff0c;成为了众多企业和机构的得力助手。其中&#xff0c;身份…

网优学习干货:王者荣耀游戏用户体验洞察及质差识别(2)

王者荣耀卡顿特点 影响时延的因素 手游定界定位解决方案 基于“9段法”进行卡顿问题分解 通过数据关联->体验定标->优化提升&#xff0c;改善手游卡顿 无线侧通过“面”和“点”优化改善空口时延 参数及互操作策略优化提升业务感知 传输优化准确定位管道问题——无TWAM…

亚信安全与鲁信科技达成合作,共筑“数字生态圈”安全未来

近日&#xff0c;亚信安全科技股份有限公司&#xff08;以下简称“亚信安全”&#xff09;正式与鲁信科技股份有限公司&#xff08;以下简称“鲁信科技”&#xff09;签订合作框架协议。双方强强携手&#xff0c;将围绕数字时代企业网络安全建设&#xff0c;在业务开拓、技术合…

创客项目秀|基于XIAO ESP32C3的本地个人助理Mr.M

作者&#xff1a;Matthew Yu 来自&#xff1a;Fab academy 在数字化时代的浪潮中&#xff0c;柴火创客空间作为创新与实践的摇篮&#xff0c;不仅为Fab Academy 2024的学员们提供了一个充满活力的学习和创作环境&#xff0c;更是将科技的力量与人文关怀深度融合。今天&#x…

学习笔记——交换——STP(生成树)简介

一、技术背景 1、生成树技术背景 交换机单线路组网&#xff0c;存在单点故障(上左图)&#xff0c;上行线路及设备都不具备冗余性&#xff0c;一旦链路或上行设备发生故障&#xff0c;业务将会中断。 为了使得网络更加健壮、更具有冗余性&#xff0c;将拓扑修改为(上右图)接入…

【直观详解】泰勒级数

非常好的一篇 泰勒展开式的 推理过程 【直观详解】泰勒级数 | Go Further | Stay Hungry, Stay Foolish 函数f(x)cos(x) 函数的逼近过程

FLUKE9500B福禄克9500B示波器校准仪

FLUKE9500B示波器校准器 福禄克9500B示波器校准仪 9500B 示波器校准器的特点 自动化示波器校准可能体现许多校准实验室中生产力的提高。如果是手动&#xff0c;则该项工作需要熟练的操作人员花费大量的时间执行基本上是重复的任务。半自动化或自动化的方案显然能够解决这一问…

2024年看项目管理软件与工程项目管理的奇妙融合

一、禅道在项目管理中的全面应用 禅道在产品管理方面&#xff0c;能够清晰地对产品的需求进行全方位管理。从需求的提出到详细信息的记录&#xff0c;再到状态、负责人以及完成进度的跟踪&#xff0c;都能有条不紊地进行。产品经理可以通过禅道制定合理的产品规划&#xff0c;…

讯飞与腾讯云:Android 语音识别服务对比选择

目录 一、讯飞语音识别 1.1 讯飞语音识别介绍 1.1.1 功能特点 1.1.2 优势 1.2 接入流程 1.2.1 注册账号并创建应用 1.2.2 下载SDK等相关资料 1.2.3 导入SDK 1.2.4 添加用户权限 1.2.5 初始化讯飞SDK 1.2.6 初始化语音识别对象 1.2.7 显示结果 二、腾讯云语音识别 …

Django一分钟:DRF生成OpenAPI接口文档

DRF项目中如果想要自动生成API文档我们可以借助drf-spectacular这个库&#xff0c;drf-spectacular非常强大&#xff0c;它可以自动从DRF中提取信息&#xff0c;自动生成API文档&#xff0c;配置简单开箱即用&#xff0c;并且它对很多常用的第三方如&#xff1a;SimpleJWT、dja…

Spark练习-RDD创建,读取hdfs上的数据,指定rdd分区

目录 RDD的创建 读取HDFS上文件数据 RDD分区指定 RDD的创建 将python数据转为rdd # 将Python数据转为rdd data [1,2,3,4] res sum(data) # 使用python的方法计算时&#xff0c;采用的单机资源计算&#xff0c;如果数据量较大时&#xff0c;可以将python数据转为spark的r…