线程池的背景
对于第一次接触线程池的人来说可能会很陌生,所以本篇博客先介绍线程池的由来。线程池的发展源自于多线程。
何为多线程:用一个通俗易懂的例子来解释:
多线程好比去银行柜台办业务。银行有四个窗口,好比四个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是通过线程池去创建一个线程单独执行这个任务。