Java线程池的使用

古城微笑少年丶 2023-02-17 03:54 95阅读 0赞

Java线程池的使用

前言

众所周知,近十年来摩尔定律逐渐在很多领域已经失效了,单个CPU的性能已经很难满足科技的需求,所以现在很多时候会采用多核的方式来提升整个服务器的性能,那么如何重复发挥多核CPU的性能,那么就不得不说使用多线程。

正文

关于线程池

为什么需要线程池?
线程是处理器调度的基本单位。我们会为每一个请求都独立创建一个线程,而操作系统创建线程、切换线程状态、结束线程都要使用CPU进行调度。

Java线程池的使用

Java当中主要有两类线程池:

  • Executor线程池:Executor是个简单的接口,它提供了一种标准的方法将任务的提交过程与执行过程解耦开来,并用Runnable来表示任务。Executor基于”生产者-消费者”模式,提交任务的操作相当于生产者,执行任务的则相当于消费者。
  • ForkjoinPool线程池:它非常适合执行可以分解子任务的任务,比如树的遍历,归并排序,或者其他一些递归场景。

Executor创建线程池的4种方式:
1.CachedThreadPool

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

CachedThreadPool的默认构造函数

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

CachedThreadPool的使用

  1. public class Main15 {
  2. public static ExecutorService mythread = Executors.newCachedThreadPool();
  3. public static void main(String[] args) {
  4. for (int i=0;i<10;i++){
  5. mythread.execute(new Runnable() {
  6. public void run() {
  7. System.out.println("执行");
  8. }
  9. });
  10. }
  11. }
  12. }

2.ScheduledThreadPool

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

ScheduledThreadPool的默认构造函数

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

ScheduledThreadPool的使用

  1. public class Main15 {
  2. public static ExecutorService mythread = Executors.newScheduledThreadPool(1024);//核心线程数
  3. public static void main(String[] args) {
  4. for (int i=0;i<10;i++){
  5. mythread.execute(new Runnable() {
  6. public void run() {
  7. System.out.println("执行");
  8. }
  9. });
  10. }
  11. }
  12. }

3.SingleThreadPool

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

SingleThreadPool的默认构造函数

  1. public static ExecutorService newSingleThreadExecutor() {
  2. return new FinalizableDelegatedExecutorService
  3. (new ThreadPoolExecutor(1, 1,
  4. 0L, TimeUnit.MILLISECONDS,
  5. new LinkedBlockingQueue<Runnable>()));
  6. }

SingleThreadPool的使用

  1. public class Main15 {
  2. public static ExecutorService mythread = Executors.newSingleThreadExecutor();
  3. public static void main(String[] args) {
  4. for (int i=0;i<10;i++){
  5. mythread.execute(new Runnable() {
  6. public void run() {
  7. System.out.println("执行");
  8. }
  9. });
  10. }
  11. }
  12. }

4.FixedThreadPool
FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程。(保证run尽可能被处理)

FixedThreadPool的默认构造函数

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

FixedThreadPool的使用

  1. public class Main15 {
  2. public static ExecutorService mythread = Executors.newFixedThreadPool(1024);
  3. public static void main(String[] args) {
  4. for (int i=0;i<10;i++){
  5. mythread.execute(new Runnable() {
  6. public void run() {
  7. System.out.println("执行");
  8. }
  9. });
  10. }
  11. }
  12. }

特别要注意的是:

  • FixedThreadPool能保证每一个Runnable对象都不会丢失,线程过小会大量进入拒绝策略中,线程数过大,但是一定程度上可能会造成OOM。在真实场景采用设置无限大,即Integer.max_value
  • 选择合适的拒绝策略,能够一定程度上保证服务的高可靠。

Executor线程池的底层原理

线程池的逻辑结构

在这里插入图片描述

创建线程池的构造函数

  1. public ThreadPoolExecutor(
  2. int corePoolSize, #核心线程数
  3. int maxinmumPoolSize, #线程总数 非核心数=总数-核心数
  4. long keepAliveTime, #当前线程数大于核心线程数时 线程的等待新任务的等待时间(核心线程也会面临死亡)
  5. TimeUnit unit, #时间单位
  6. BlockingQueue<Runnable> workQueue #任务队列
  7. RejectedExecutionHandler #(选填) 拒绝处理器
  8. )

ThreadPoolExecutor线程池处理线程的过程:

  • 当前运行线程数 小于corePoolSize 任务直接交给核心线程进行执行、
  • 当前运行线程数 大于或等于 corePoolSize 任务且满足队列未满,那么 任务将进入任务队列进行等待,并且任务队列都具有阻塞性,所以只有当核心线程数的任务执行完了,才会从任务队列中获取任务。
  • 当前运行线程数 大于或等于 corePoolSize 任务且队列已满,那么 任务进入非核心线程。
  • 当核心线程、等待队列、非核心线程都被占用的时候线程会被拒绝器处理。

阻塞特性:当队列满了,便会阻塞等待,直到有元素出队,后续的元素才可以被加入队列。

任务队列

任务队列实现BlockingQueue接口,即阻塞队列接口,具体有:

  • SyschronousQueue:每一次add()插入 必须要等待相对删除/读取操作
  • ArrayBlockingQueue:数组的方式,大小创建后不能改变大小,具有阻塞特性。
  • LinkedBlockingQueue:无限容量 基于链表的形式
  • LinkedBlockingDeque :无限双端链表队列,可以从两头进行元素的读/取操作
  • PriorityBlockingQueue:按照优先级进行内部元素排序的无限队列。
  • LinkedTransferQueue:无限队列,先进先出,具有阻塞特性。

拒绝处理器
适用:那些既不能进入核心线程、等待队列,也无法使用非核心线程来处理,或者线程异常的线程:

  • CallerRunsPolicy:直接运行该任务的run方法,但不是在线程池内部,适合处理业务比较重要且数量不多的场景。
  • AbortPolicy:RejectedExecutionException异常抛出。适用对业务非常重要的完全不能不执行的场景。(默认)
  • DiscardPolicy:不会做任何处理。适合处理丢失对业务影响不大的场景。
  • DiscardOldestPolicy:检查等待队列 强行取出队列头部任务(并抛弃该头部任务)后 再进行执行该任务。适合新数据比旧数据重要的场景。

特定场景如何去设置线程池
线程池的关键点是:

  1. 尽量减少线程切换和管理的开支;
  2. 最大化利用CPU

对于1,要求线程数尽量少,这样可以减少线程切换和管理的开支;
对于2,要求尽量多的线程,以保证CPU资源最大化的利用。

根据访问量场景给与以下的建议

  • 高并发,低耗时的情况:建议少线程,只要满足并发即可;例如并发100,线程池可能设置为10就可以。
  • 低并发,高耗时的情况:建议多线程,保证有空闲线程,接受新的任务;例如并发10,线程池可能就要设置为20;
  • 高并发高耗时:1要分析任务类型,2增加排队,3、加大线程数

在springboot中的使用线程池

1.方式一:以注解的方式使用

  1. @Configuration
  2. @EnableAsync
  3. public class TaskPoolConfig {
  4. @Bean("taskExecutor")
  5. public Executor taskExecutro(){
  6. ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
  7. taskExecutor.setCorePoolSize(10);//设置核心线程数
  8. taskExecutor.setMaxPoolSize(50);//设置最大线程数
  9. taskExecutor.setQueueCapacity(200);//线程池使用缓冲队列大小
  10. taskExecutor.setKeepAliveSeconds(60);//最大存活时间
  11. taskExecutor.setThreadNamePrefix("taskExecutor--");//设置线程名称的前缀
  12. taskExecutor.setWaitForTasksToCompleteOnShutdown(true);//当调度器shutdown被调用时等待当前被调度的任务完成
  13. taskExecutor.setAwaitTerminationSeconds(60);//当调度器执行shutdown时,等待60秒后进行关闭
  14. return taskExecutor;
  15. }
  16. /** * 使用模板 * @param i */
  17. @Async("taskExecutor")
  18. public void tesTask(int i){
  19. System.out.println(Thread.currentThread().getName()+"-----"+i);
  20. }
  21. }

2.以XML配置的方式来使用

  1. <!-- 订单线程池 -->
  2. <bean id="syncOrderExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
  3. <!-- 线程池维护线程的最少数量 -->
  4. <property name="corePoolSize" value="1" />
  5. <!-- 允许的空闲时间 -->
  6. <property name="keepAliveSeconds" value="200" />
  7. <!-- 线程池维护线程的最大数量 -->
  8. <property name="maxPoolSize" value="5" />
  9. <!-- 缓存队列 -->
  10. <property name="queueCapacity" value="2000" />
  11. <!-- 对拒绝task的处理策略 -->
  12. <property name="rejectedExecutionHandler">
  13. <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
  14. </property>
  15. </bean>

源码

项目源码可从的我的github中获取:github源码地址

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,95人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java线使用

    Java线程池的使用 前言 众所周知,近十年来摩尔定律逐渐在很多领域已经失效了,单个`CPU`的性能已经很难满足科技的需求,所以现在很多时候会采用多核的方式来提升整

    相关 Java线使用

    对于线程池的使用,应该将线程池置为单例模式,节省内存的开销,线程池线程的数量可以根据业务,内核,cup等进行评估。 常用的线程池是使用java并发包中的ThreadPool

    相关 Java线使用

     如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。   那么有没有一种办法

    相关 Java线使用

    线程池(Thread Pool): 一种线程的使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。