zxpnet网站 zxpnet网站
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

zxpnet

一个爱学习的java开发攻城狮
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 大后端课程视频归档
  • 南航面试题
  • 并发编程

  • 性能调优

  • java8语法

  • lombok

  • 日志

  • 工具类

  • spring

  • mybatis

  • springboot

    • springboot基础篇
    • springboot配置文件yaml
    • springboot web开发
    • springboot数据访问
    • springboot单元测试
    • springboot性能监控actuator
    • SpringBoot 原理解析
    • spring.factories文件
    • springboot常用注解
    • springboot序列化
    • springboot整合swagger
    • Spring Boot 定制URL匹配规则的方法
    • springboot整合redisson分布式锁
    • springboot+线程池使用
      • springboot2应用中碰到的一些坑
      • SpringBoot面试题
    • redis

    • zookeeper

    • springcloud

    • dubbo

    • netty

    • springsecurity

    • mq消息中间件

    • shiro

    • beetle

    • 模板引擎

    • jpa

    • 数据结构与算法

    • 数据库知识与设计

    • gradle

    • maven

    • bus

    • 定时任务

    • docker

    • centos

    • 加解密

    • biz业务

    • pigx项目

    • 开源项目

    • 品达通用权限项目-黑马

    • 货币交易项目coin-尚学堂

    • php

    • backend
    • springboot
    shollin
    2021-08-03
    目录

    springboot+线程池使用

    • 一、什么是线程
    • 二、线程的生命周期
    • 三、什么是单线程和多线程?
    • 四,线程池
    • 五,四种常见的线程池:
    • 六,线程池

    # 一、什么是线程

    线程,程序执行的最小执行单位,是行程中的实际运作单位,经常容易和进程这个概念混淆。那么,线程和进程究竟有什么区别呢?首先,进程是一个动态的过程,是一个活动的实体。简单来说,一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执行者。可以说,进程中包含了多个可以同时运行的线程。

    # 二、线程的生命周期

    线程的生命周期可以利用以下的图解来更好的理解: img

    第一步,是用new Thread()的方法新建一个线程,在线程创建完成之后,线程就进入了就绪(Runnable)状态,此时创建出来的线程进入抢占CPU资源的状态,当线程抢到了CPU的执行权之后,线程就进入了运行状态(Running),当该线程的任务执行完成之后或者是非常态的调用的stop()方法之后,线程就进入了死亡状态。而我们在图解中可以看出,线程还具有一个阻塞的过程,这是怎么回事呢?当面对以下几种情况的时候,容易造成线程阻塞,第一种,当线程主动调用了sleep()方法时,线程会进入则阻塞状态,除此之外,当线程中主动调用了阻塞时的IO方法时,这个方法有一个返回参数,当参数返回之前,线程也会进入阻塞状态,还有一种情况,当线程进入正在等待某个通知时,会进入阻塞状态。那么,为什么会有阻塞状态出现呢?我们都知道,CPU的资源是十分宝贵的,所以,当线程正在进行某种不确定时长的任务时,Java就会收回CPU的执行权,从而合理应用CPU的资源。我们根据图可以看出,线程在阻塞过程结束之后,会重新进入就绪状态,重新抢夺CPU资源。这时候,我们可能会产生一个疑问,如何跳出阻塞过程呢?又以上几种可能造成线程阻塞的情况来看,都是存在一个时间限制的,当sleep()方法的睡眠时长过去后,线程就自动跳出了阻塞状态,第二种则是在返回了一个参数之后,在获取到了等待的通知时,就自动跳出了线程的阻塞过程

    # 三、什么是单线程和多线程?

    单线程,顾名思义即是只有一条线程在执行任务,这种情况在我们日常的工作学习中很少遇到,所以我们只是简单做一下了解

    多线程,创建多条线程同时执行任务,这种方式在我们的日常生活中比较常见。但是,在多线程的使用过程中,还有许多需要我们了解的概念。比如,在理解上并行和并发的区别,以及在实际应用的过程中多线程的安全问题,对此,我们需要进行详细的了解。

    并行和并发:在我们看来,都是可以同时执行多种任务,那么,到底他们二者有什么区别呢?

    并发,从宏观方面来说,并发就是同时进行多种时间,实际上,这几种时间,并不是同时进行的,而是交替进行的,而由于CPU的运算速度非常的快,会造成我们的一种错觉,就是在同一时间内进行了多种事情

    而并发,则是真正意义上的同时进行多种事情。这种只可以在多核CPU的基础下完成。

    还有就是多线程的安全问题?为什么会造成多线程的安全问题呢?我们可以想象一下,如果多个线程同时执行一个任务,name意味着他们共享同一种资源,由于线程CPU的资源不一定可以被谁抢占到,这是,第一条线程先抢占到CPU资源,他刚刚进行了第一次操作,而此时第二条线程抢占到了CPU的资源,name,共享资源还来不及发生变化,就同时有两条数据使用了同一条资源,具体请参考多线程买票问题。这个问题我们应该如何解决那?

    有造成问题的原因我们可以看出,这个问题主要的矛盾在于,CPU的使用权抢占和资源的共享发生了冲突,解决时,我们只需要让一条线程战歌了CPU的资源时,阻止第二条线程同时抢占CPU的执行权,在代码中,我们只需要在方法中使用同步代码块即可。在这里,同步代码块不多进行赘述,可以自行了解。

    # 四,线程池

    由以上介绍我们可以看出,在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。

    线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。

    那么,我们应该如何创建一个线程池那?Java中已经提供了创建线程池的一个类:Executor

    而我们创建时,一般使用它的子类:ThreadPoolExecutor

    public ThreadPoolExecutor(int corePoolSize, 
                   int maximumPoolSize, 
                   long keepAliveTime, 
                   TimeUnit unit, 
                   BlockingQueue workQueue, 
                   ThreadFactory threadFactory, 
                   RejectedExecutionHandler handler)
    
    1
    2
    3
    4
    5
    6
    7

    这是其中最重要的一个构造方法,这个方法决定了创建出来的线程池的各种属性,下面依靠一张图来更好的理解线程池和这几个参数:

    img

    又图中,我们可以看出,线程池中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收,maximumPoolSize就是线程池中可以容纳的最大线程的数量,而keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间,而util,就是计算这个时间的一个单位,workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。threadFactory,就是创建线程的线程工厂,最后一个handler,是一种拒绝策略,我们可以在任务满了知乎,拒绝执行某些任务。

    线程池的执行流程又是怎样的呢?

    img

    有图我们可以看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。

    handler的拒绝策略:

    有四种:

    第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满,为默认策略

    第二种DisCardPolicy:不执行新任务,也不抛出异常,抛弃新任务

    第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行

    第四种CallerRunsPolicy:直接调用execute来执行当前任务

    # 五,四种常见的线程池:

    CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。

    SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。

    SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。

    FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程

    # 六,线程池

    # 1、配置

    /**
     * 线程池配置  https://juejin.cn/post/6844903584857849870
     * 参考Ruoyi、JobTriggerPoolHelper的参数配置
     * task.pool.corePoolSize=50
     * task.pool.maxPoolSize=200
     * task.pool.keepAliveSeconds=300
     * task.pool.queueCapacity=1000
     *
     * @author zxp
     * @date 2021/8/3 11:28
     */
    @ConfigurationProperties(prefix = "task.pool")
    @Data
    public class TaskThreadPoolProperties {
    
        //核心线程池大小
        private Integer corePoolSize=50;
        // 总线程数量=核心线程+非核心线程
        private Integer maxPoolSize=100;
    
        // 线程池维护线程所允许的空闲时间,超过这个时间,非核心线程就会回收
        private Integer keepAliveSeconds=300;
        //队列容量
        private Integer queueCapacity=1000;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    # 2. execute(Runable)方法执行过程

    如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

    如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

    如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maxPoolSize,建新的线程来处理被添加的任务。

    如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maxPoolSize,那么通过handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

    当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

    # 2、生成Executor线程池bean

    # 3、整合spring原生线程池

    实现AsyncConfigurer接口即可, 这里将2、3步一起进行

    /**
     * 参考文章 :https://juejin.cn/post/6844903584857849870
     * 原生(Spring)异步任务线程池装配类
     * 实现 implements AsyncConfigurer,可以替换默认的线程池
     * @author zxp
     * @date 2021/8/3 11:32
     */
    @Configuration
    @EnableConfigurationProperties(TaskThreadPoolProperties.class)
    @EnableAsync
    @RequiredArgsConstructor
    @Slf4j
    public class MyThreadPoolConfig implements AsyncConfigurer {
    
        private final TaskThreadPoolProperties config;
    
        @Bean(name="threadPoolTaskExecutor")
        @Override
        public Executor getAsyncExecutor() {
    
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            //核心线程池大小
            executor.setCorePoolSize(config.getCorePoolSize());
            //最大线程数
            executor.setMaxPoolSize(config.getMaxPoolSize());
            //队列容量
            executor.setQueueCapacity(config.getQueueCapacity());
            //活跃时间
            executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
            //线程名字前缀
            executor.setThreadNamePrefix("ZxpExecutor-");
    
            // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
            // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            executor.initialize();
            return executor;
        }
        
        /**
         * 执行周期性或定时任务
         */
        @Bean(name = "scheduledExecutorService")
        protected ScheduledExecutorService scheduledExecutorService()
        {
            return new ScheduledThreadPoolExecutor(corePoolSize,
                    new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build())
            {
                @Override
                protected void afterExecute(Runnable r, Throwable t)
                {
                    super.afterExecute(r, t);
                    Threads.printException(r, t);
                }
            };
        }
    
        /**
         *  异步任务中异常处理
         * @return
         */
        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return new AsyncUncaughtExceptionHandler() {
                @Override
                public void handleUncaughtException(Throwable arg0, Method arg1, Object... arg2) {
                    log.error("======"+arg0.getMessage()+"=====", arg0);
                    log.error("exception method:"+arg1.getName());
                }
            };
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72

    以上是架构形式的写法,需要将MyThreadPoolConfig配置到spring.factories文件里面

    # 4、使用

    @Component
    @Slf4j
    public class AsyncTask {
    
        @Async("myTaskAsyncPool")
        public void doTask1(int i) throws InterruptedException{
            log.info("Task1"+i+" started.");
        }
    
        @Async
        public void doTask2(int i) throws InterruptedException{
            log.info("Task2-Native"+i+" started.");
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    @Async和@EnableAsync要结合使用,才能发挥异步的效果

    建议把所有带有@Async的方法都放到同一个类里,不然很容易出现循环依赖的问题

    参考文章

    springboot+线程池使用 - luck-monkey - 博客园 (cnblogs.com) (opens new window)

    SpringBoot 自定义线程池 (juejin.cn) (opens new window)

    springboot整合redisson分布式锁
    springboot2应用中碰到的一些坑

    ← springboot整合redisson分布式锁 springboot2应用中碰到的一些坑→

    最近更新
    01
    国际象棋
    09-15
    02
    成语
    09-15
    03
    自然拼读
    09-15
    更多文章>
    Theme by Vdoing | Copyright © 2019-2023 zxpnet | 粤ICP备14079330号-1
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式