并发编程系列之如何正确使用线程池?

并发编程系列博客

原文链接

并发编程系列之如何正确使用线程池?在上一章节的学习中,我们掌握了线程的基本知识,接着本博客会继续学习多线程中的线程池知识

1、线程是不是越多越好?

在学习多线程之前,读者可能会有疑问?如果单线程跑得太慢,那么是否就能多创建多个线程来跑任务?并发的情况,线程是不是创建越多越好?这是一个很经典的问题,画图表示一下创建很多线程的情况,然后进行情况分析。

并发编程系列之如何正确使用线程池?
  • 创建线程和销毁线程都是需要时间的,如果创建时间+销毁时间>执行任务时间就很不划算
  • 创建后的线程是需要内存去存放的,创建的线程对应一个Thread对象,对象是会占用JVM的堆内存的,根据jvm规范,一个线程默认最大栈大小为1M,这个栈空间也是需要从系统内存中分配的,所以线程越多,需要的内存就越多
  • 创建线程, 操作系统是需要频繁进行线程上下文切换的,所以线程创建太多,是会影响性能的

上下文切换(context switch):对于单核CPU来说,在一个时刻只能运行一个线程,对于并行来说,单核cpu也是可以支持多线程执行代码的,CPU是通过给线程分配时间片来解决的,所谓时间片是CPU给每个线程分配的时间,时间片的时间是非常短的,所以执行完成一个时间片后,进行任务切换,切换之前先保存这个任务的状态,以便于下次换回来的时候,可以加载这个任务的状态,所以从保存任务状态到再加载任务的过程称为上下文切换,不仅在线程间可以上下文切换,进程也同样可以

2、如何正确使用多线程?

  • 如果是计算型任务?
    CPU数量的1~2倍即可
  • 如果是IO密集型任务?
    就需要多一些线程,要根据具体的io阻塞时长来进行考量决定

3、Java线程池的工作原理

并发编程系列之如何正确使用线程池?
  • 接收任务,放入线程池的任务仓库
  • 工作线程从线程池的任务仓库取,执行
  • 没有任务时,线程阻塞,有任务时唤醒线程

4、掌握JUC线程池API

  • Executor : 接口类
  • ExecutorService:加入关闭方法和对Runnable、Callable、Future的支持
    并发编程系列之如何正确使用线程池?
  • shutdown:已经提交的会执行完成
  • shutdownNow:正在执行的会执行完成,未来执行的返回
  • awaitTermination:阻塞等待任务关闭完成
  • submit类型的:都是提交任务的,支持Runnable和Callable
  • invokeAll类型的:执行集合中所有任务
  • ScheduleExecutorService :加入对定时任务的支持
    并发编程系列之如何正确使用线程池?
    其中 schedule(Runablle , long, Timeunit)schedule(Callable<v> , long, TimeUnit)</v>表示的是多久后执行,而 scheduleAtFixedRate方法和 scheduleWithFixedDelay方法表示的都是周期性重复执行的

再描述 scheduleAtFixedRate方法和 scheduleWithFixedDelay方法的区别:
scheduleAtFixedRate:以固定的时间频率重复执行任务,如每10s ,也就是两个任务直接以固定的时间间隔执行,不管任务执行完成与否

并发编程系列之如何正确使用线程池?

scheduleWithFixedDelay:以固定的任务时延迟来重复执行任务,这种任务不管任务执行多久都执行完成,然后隔预定的如3s,接着执行下一个任务,每个任务之间的间隔都是一样的

并发编程系列之如何正确使用线程池?
  • Executors:快速得到线程池的工具类,创建线程池的工厂类
  • newFixedThreadPool(int nThreads):创建一个固定大小、任务队列 无界的线程池。线程池的核心线程数=最大线程池=nThreads
  • newCachedThreadPool():创建的是一个大小无界的缓冲线程池。它的任务队列是一个同步队列。如果队列中有空闲的线程,则用空闲线程执行,如果没有就创建新线程执行。池中线程空闲超过60s,就会被释放。缓冲线程池使用于执行耗时比较小的异步任务。线程池的核心线程数=0,最大线程池= Integer.MAX_VALUE
  • newSingleThreadExecutor():创建的是只有一个线程来执行无界任务队列的单一线程池。该线程池按顺序执行一个一个加入的任务,任何时刻都只有一个线程在执行。单一线程池和 newFixedThreadPool(1)的区别在于,单一线程池的池大小是不能再改变的
  • newScheduleThreadPool(int corePoolSize): 能定时执行任务的线程池,该池的核心线程数由参数 corePoolSize指定,最大线程数= Integer.MAX_VALUE
  • newWorkStealingPool():以当前系统可用的处理器数作为并行级别创建的 work-stealing thread pool(ForkJoinPool)
  • newWorkStealingPool(int parallelism):以指定的 parallelism并行级别创建的 work-stealing thread pool(ForkJoinPool)
  • ThreadPoolExecutor:线程池的标准实现
    并发编程系列之如何正确使用线程池?
    下面列举出ThreadPoolExecutor的主要参数:

参数 描述 corePoolSize 核心线程数量 maxPoolSize 最大线程数量 keepAliveTime+时间单位 空闲线程的存活时间 ThreadFactory 线程工厂,用于创建线程 workQueue 用于存放任务的队列,可以称之为工作队列 Handler 用于处理被拒绝的任务

虽然Executors使用起来很方便,不过在阿里编程规范里是强调了慎用Executors创建线程池,下面摘录自阿里编程规范手册:

【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,
这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

ThreadPoolExecutor的基本参数:

new ThreadPoolExecutor(
                2, // 核心线程数
                5, // 最大线程数
                60L, // keepAliveTime,线程空闲超过这个数,就会被销毁释放
                TimeUnit.SECONDS, // keepAliveTime的时间单位
                new ArrayBlockingQueue(5)); // 传入边界为5的工作队列

画流程图表示,线程池的核心参数是corePoolSize、maxPoolSize、workQueue(工作队列)

并发编程系列之如何正确使用线程池?
线程池工作原理示意图:任务可以一直放,直到线程池满了的情况,才会拒绝,然后除了核心线程,其它的线程会被合理回收。所以正常情况下,线程池中的线程数量会处在 corePoolSize 与 maximumPoolSize 的闭区间内
并发编程系列之如何正确使用线程池?
ThreadPoolExecutor基本实例:
ExecutorService service = new ThreadPoolExecutor(2, 5,
                60L, TimeUnit.SECONDS,
                new ArrayBlockingQueue(5));
 service.execute(() ->{
     System.out.println(String.format("thread name:%s",Thread.currentThread().getName()));
 });
  // 避免内存泄露,记得关闭线程池
 service.shutdown();

ThreadPoolExecutor加上Callable、Future使用的例子:

public static void  main(String[] args) {
    ExecutorService service = new ThreadPoolExecutor(2, 5,
                    60L, TimeUnit.SECONDS,
                    new ArrayBlockingQueue(5));

    Future future = service.submit(new CallableTask());
    Thread.sleep(3000);
    System.out.println("future is done?" + future.isDone());
    if (future.isDone()) {
        System.out.println("callableTask返回参数:"+future.get());
    }
    service.shutdown();
}

static class CallableTask implements Callable{
     @Override
     public Integer call() {
         return ThreadLocalRandom.current().ints(0, (99 + 1)).limit(1).findFirst().getAsInt();
     }
 }

Original: https://www.cnblogs.com/mzq123/p/15216738.html
Author: smileNicky
Title: 并发编程系列之如何正确使用线程池?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/541712/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 静态代理、动态代理与Mybatis的理解

    静态代理、动态代理与Mybatis的理解 这里的 代理与设计模式中的代理模式密切相关,代理模式的主要作用是为其他对象提供一种控制对这个对象的访问方法,即在一个对象不适合或者不能直接…

    Java 2023年6月8日
    070
  • 简单的决策树以及代码实现

    简单的决策树 1. 本篇文章介绍 ​ 这篇文章旨在介绍最简易的决策树以及其复现代码,让大家基本了解决策树的基本流程,而不是被各种其它的诸如数据处理,剪枝等操作给搞迷糊了。 ​ 我的…

    Java 2023年6月7日
    082
  • Git的使用以及整理

    Usage of Git 1 Git区域划分 1)工作区(working directory):默认为项目根目录root 2)缓存区(stage):在版本库中设立一个缓存/暂存区,…

    Java 2023年6月8日
    091
  • 【JavaWeb】JDK动态代理 — 代理模式;静态代理;动态代理;JDK动态代理的实现及实现原理分析

    需要掌握的程度: 什么是JDK动态代理 ? 使用jdk的反射机制,创建对象的能力(创建的是代理类的对象),而不用创建类文件,不用写java文件。 动态:在程序执行时,调用jdk提供…

    Java 2023年6月8日
    074
  • 不止面试-JVM垃圾回收面试题详解

    第一部分:面试题 本次分享我们将尝试回答以下问题: GC 是什么? 为什么要有 GC? 简单说一下java的垃圾回收机制。 JVM的常见垃圾回收算法有哪些? 为什么要使用分代回收机…

    Java 2023年6月15日
    047
  • 30个类手写Spring核心原理之AOP代码织入(5)

    本文节选自《Spring 5核心原理》 前面我们已经完成了Spring IoC、DI、MVC三大核心模块的功能,并保证了功能可用。接下来要完成Spring的另一个核心模块—AOP,…

    Java 2023年6月7日
    072
  • Unit 1 Computer hardware【石家庄铁道大学-专业英语课 】

    Unit 1 Computer hardware 1、Introduction of computer A computer is a machine that can be in…

    Java 2023年6月15日
    060
  • MyBatis 缓存机制

    MyBatis 包含一个非常强大的查询缓存特性,它可以非 常方便地配置和定制。缓存可以极大的提升查询效率。 MyBatis 系统中默认定义了两级缓存,也就是 一级缓存和二级缓存: …

    Java 2023年6月8日
    067
  • Spring Ioc源码分析系列–自动注入循环依赖的处理

    Spring Ioc源码分析系列–自动注入循环依赖的处理 前言 前面的文章Spring Ioc源码分析系列–Bean实例化过程(二)在讲解到Spring创建…

    Java 2023年6月8日
    072
  • Java 8 Lambda表达式介绍(二)

    接着上篇内容 函数式编程接口 从上面的代码例子可以看出,我们使用Lambda表达式创建线程的时候, 并不关心接口名,方法名,参数名。我们 只关注他的参数类型,参数个数,返回值。 J…

    Java 2023年5月29日
    062
  • Nginx 通过 /api 前缀和二级域名进行反向代理

    当我们在一台服务器上 启动多个服务时, 因为在 http 协议下默认端口是 80 端口, https 下默认是 443 端口,为了好记和美观我们 只能对外暴漏这两个端口。 以 ht…

    Java 2023年5月30日
    088
  • shiro AuthenticationToken体系

    shiro AuthenticationToken体系 如上图,为AuthenticationToken的继承体系,AuthenticationToken是在shiro进行身份验证…

    Java 2023年6月7日
    091
  • 彻底消灭if-else嵌套

    一、背景 1.1 反面教材 不知大家有没遇到过像 横放着的金字塔一样的 if-else嵌套: if (true) { if (true) { if (true) { if (tru…

    Java 2023年6月5日
    084
  • SpringBoot集成消息队列

    背景 最近在对公司开发框架进行优化,框架内涉及到多处入库的日志记录,例如登录日志/操作日志/访问日志/业务执行日志,集成在业务代码中耦合度较高且占用业务操作执行时间,所以准备集成相…

    Java 2023年6月15日
    071
  • rocketmq 如何保证顺序消费

    rocketMQ 为了保证 consumer 顺序消费,做了很多工作。 MQClientManager 在 jvm 进程中是单例,其内部维护一个 map,键是 clientId,值…

    Java 2023年5月30日
    052
  • Executors提供的四种线程池

    Executors提供的四种线程池 怀有希望!! posted @2017-05-25 11:15 人生设计师 阅读(342 ) 评论() 编辑 Original: https:/…

    Java 2023年5月29日
    071
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球