线程池andSpringBoot是如何实现异步任务(@Async)

线程池的背景

对于第一次接触线程池的人来说可能会很陌生,所以本篇博客先介绍线程池的由来。线程池的发展源自于多线程。

何为多线程:用一个通俗易懂的例子来解释:

多线程好比去银行柜台办业务。银行有四个窗口,好比四个CPU。如果只开一个线程的话,相当于所有办业务的人都排成一队挤在一个窗口办业务,或者说每个人都必须等上一个人办完业务之后随机选一个窗口,总之就是串行一个个来。如果开了四个线程的话,那么就是四个窗口同时都可以办理业务,可以同时服务四个人,办事效率就提高了,不用都挤在一起排成长长的一队。所以这体现了多线程优点的就是可以充分利用现在计算机配置多核cpu的硬件特性,把多核cpu利用起来提高任务的处理效率。

既然如此:多线程具有提高任务效率的优势,那么为什么会出现线程池这个东子呢。

传统多线程创建的缺点:
传统的创建线程方式是通过new Thred()方法去创建一个独立于当前线程的线程。使得该创建出来的线程完成某种功能。但是这种方式有着明显的缺点:首先我们在创建线程的个数上面,无法做到合理的控制。并且需要自己编写代码进行线程的销毁。同时导致了一个问题就是,当我们需要多线程的时候,我们就回去创建一个线程,执行完成之后就会去销毁他。这样就会有出现线程频繁的创建和销毁导致系统I/O资源的浪费。

所以我们就要想出一种解决方案,是否可以解决,传统多线程创建过程中出现的,并发数目不好控制,以及线程多次创建和销毁导致的I/O资源的浪费。这也就是线程池的由来。

什么是线程池:

我觉得线程池就是一容器,是创建多个线程并且管理的容器。(线程池是一个容器,可以创建和管理线程,并且分批任务)

为什么要用线程池:

上面我们说过了:以前在我们Java里面要去创建线程,需要new Thread()。这样去开启一个新的线程,那如果要创建很多线程的话,那是不是要一直new Thread。这明显是很不科学的一件事情。举一个例子:在我们项目里面,如果全部是用New Thread的方式去创建线程的,那么在1线程创建好的时候,又创建线程2,一次类推,这样会导致,假设后面的任务执行完了,但是前面的线程没有被销毁,这就会一次创建线程下去,这就是对资源的一种浪费。

这种线程缺乏统一管理,统一执行,定时,中断的功能。所以这时候线程池的优势就出来了

线程池的好处:

1.重用已经存在的线程,减少线程的创建和销毁,较少资源的消耗

2.可以控制最大的并发数,提高资源的利用率和减少竞争。降低死锁出现的概率

3.可以提供定时和定期执行任务的功能

反正线程池就是使得对线程的管理更加方便

4种实现线程池的方式

线程池都继承了ExecutorService,所以在创建的时候是,已经是运行的状态了,当没有任务执行的时候,它就关闭了,但是只有执行shutdown()方法的时候这个线程池子才被销毁了。

实现线程池子的方式一般来说是四种

fixedThreadPool() //启动固定线程数的线程池

CachedThreadPool() //按需分配的线程池

ScheduledThreadPoolExecutor()//定时,定期执行任务的线程池

SingleThreadExecutor()

以上四种是实现线程池的集中基本方式,但是这几种方式全都是建立在ThreadPoolExecutor()方法上面的。所以该方法是最最基础的。我们接下来就来讲一下ThreadPoolExecutor()//指定线程数的线程池。

1.ThreadPoolExecutor()//指定线程数的线程池

我们先看该方法的源代码,也可以看到该方法有这么多的参数形式。那么我们就下来就来解释一下。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

先介绍一下参数:

首先corePoolSize,核心线程数,我的理解就是一个阈值,比如某个时候线程数目是小于这个数量,这时候一个任务进来了,哪怕你有线程空闲,但是它不会去启用那个空闲都线程来执行这个任务,而是会直接创建一个线程去执行这个任务。

maximumPoolSize 最大线程数

最大线程数表示这个线程池能够创建线程数目的最大值:那么什么时候线程数组会超过核心线程数目呢?

当我们的线程数达到核心线程数目的时候,这时候如果有心得任务进来,因为现在所有的线程都没有空闲。同时,当前的线程数目已经达到了核心线程数目,这时候该线程池是不会去创建心得线程来执行这个任务,他会把任务放进任务队列里面去。当我们如果有一个线程执行完任务空闲出来了,那么,他就会去任务队列里面去去任务出来执行。

那么?如果任务队列里面的任务满了呢?那么这个时候就会就会创建新的线程去执行任务了。

  当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
  当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常

keepAliveTime  这是为了控制线程数目回到核心线程数的空闲时间阈值,当我们线程的空闲时间达到了这个值他就要被回收

TimeUnit unit,空闲时间的单位

workQueue  任务队列,如果线程池达到了核心线程数,但是其他线程都处在活跃的状态,新来的任务要放到任务队列里面去。

Handler:拒绝策略,当workQueue队满时,采取的措施。这个地方需要请看下面这篇博客(骗骗访问量)

https://blog.csdn.net/qq_41723573/article/details/108258053

2.fixedThreadPool() //启动固定线程数的线程池

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

创建的时候一般会给一个线程数,是目的要求创建的线程的数量,这个的话,看底层的代码其实是第一种方法里面的核心线程数,包括最大的线程数也是核心线程数,因为核心线程数和最大线程数是一样的,所以其实不需要最大空闲时间,因为线程数目是不会超过最大线程数。但是,它采用的任务队列是没有边界的LinkedBlockingQueue,所以可以容纳无数个任务。

3.CachedThreadPool() //按需分配的线程池

创建方式又两种,第一种是按需分配,他的核心线程是0,但是最大线程数是最大。这就是按需分配,怎么个按需分配呢?他当你一个任务进来,这时候要是线程都是活跃的,那么它会在创建一个线程去执行这个任务。那么线程会这样一直创建下去吗,当然不是,它有一个空闲时间,要是一个线程他的空闲时间超过了这个值,那这个线程就要被关闭了

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

    /**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available, and uses the provided
     * ThreadFactory to create new threads when needed.
     * @param threadFactory the factory to use when creating new threads
     * @return the newly created thread pool
     * @throws NullPointerException if threadFactory is null
     */
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

4.ScheduledThreadPoolExecutor()可做定时器

创建的时候也只是需要传入你要创建的核心线程数即可,

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }



 public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }



public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

这个线程池的创建方式其实和前面Fix没有什么不一样,关键是在于,这个线程池是可以执行定时任务的/怎么执行呢?那就呀实现上面的方法。

第一个为要执行的任务。

第二个为每次任务执行的延迟,比如传入1,就会每隔1秒执行一次。

第三个为执行的周期

第四个为第二个参数的时间单位

实现是这样的:匿名内部类写法。

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"----->" + fibc(40));
            }
        },5,3,TimeUnit.SECONDS);

SpringBoot是如何实现异步任务(@Async)

使用一个功能其实分为以下几步,因为springboot框架很多东西都帮我们集成好了,所以还是比骄傲简单。

1.导入依赖

2.如果需要配置,那就在yaml文件里面进行配置就可以了

3.在启动类启动该功能

4.使用它

@Async的作用是什么呢?:它被称为异步方法,也就是一个方法要是被这个注解修饰的话,那么在这个方法就是异步方法,会在一个独立的线程里面执行。调用者无需等待他的完成。

看了下源码,一大堆注解,什么玩意

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
    String value() default "";
}

 借鉴了别人的博客:

找到后面的实现类是:

private String poolSize;//线程池大小
    private Integer queueCapacity;//队列容量
    private RejectedExecutionHandler rejectedExecutionHandler;//拒绝定义
    private Integer keepAliveSeconds;  //空闲时间
    private String beanName;
    private ThreadPoolTaskExecutor target;  线程池
public void afterPropertiesSet() {
        this.target = new ThreadPoolTaskExecutor();
        this.determinePoolSizeRange();
        if (this.queueCapacity != null) {
            this.target.setQueueCapacity(this.queueCapacity);
        }

        if (this.keepAliveSeconds != null) {
            this.target.setKeepAliveSeconds(this.keepAliveSeconds);
        }

        if (this.rejectedExecutionHandler != null) {
            this.target.setRejectedExecutionHandler(this.rejectedExecutionHandler);
        }

        if (this.beanName != null) {
            this.target.setThreadNamePrefix(this.beanName + "-");
        }

        this.target.afterPropertiesSet();
    }

所以本质其实iu是通过线程池去创建一个线程单独执行这个任务。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值