引言

前面花了很多时间把线程池的核心容器和主要核心流程源码大概的分析了一遍,如果有认真看了的话相信,一定对于线程池有了较深的理解,ThreadPoolExecutor是线程池框架的一个核心类,通过对ThreadPoolExecutor的分析,可以知道其对资源进行了复用,并非无限制的创建线程,可以有效的减少线程创建和切换的开销,使用起来也不在话下,这篇就简单应用下线程池。

一、线程池的意义

合理利用线程池不仅能让我们的编码风格更规范更优雅,而且还能提高我们的应用性能,提高用户体验。具体体现在:

  • 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
  • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
  • 提供高效的定时执行、定期执行、单线程、并发数控制等功能

二、四种系统线程池的简单使用

通过Executor框架的工厂类Executors,可以通过Executors的静态方法来创建四种类型的线程池,一般来说采取默认的饱和策略就可以了,如果需要传入自定义的饱和策略,可以调用对应的重载构造方法通过ThreadFactory参数传入。本质上说就是不同的类型采用了不同的阻塞队列,线程池的使用很简单,主要通过四步:

  • 通过Executors创建对应的线程池
  • 创建自己的工作线程Runnable或者Callable
  • 通过sumbmit或者execute提交
  • 根据需要关闭线程池

1、通过Executors.newFixedThreadPool() 创建可重用固定线程数的线程池

初始化一个指定线程数的线程池,其中核心线程池大小等于线程池大小,采用LinkedBlockingQueue且容量为Integer。MAX_VALUE,实际现场数量永远维持在mThreads,因此核心线程池大小和线程池大小相当于是无效的,虽然keepAliveTime为0,但当线程池没有可执行任务时,也不会释放线程即keepAliveTime无效。

 /*** corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。当线程池中的线程数大于corePoolSize时,* keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止。*/public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());//容量为Integer.MAX_VALUE,太奢侈了吧}
这里写代码片

2、通过Executors.newSingleThreadExecutor() 单个worker线程的线程池

初始化的线程池中有且只有一个工作线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行,其实使用装饰模式增强了ScheduledExecutorService的功能,不仅确保只有一个线程顺序执行任务,也保证线程意外终止后会重新创建一个线程继续执行任务。

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

3、通过Executors.newCachedThreadPool() 根据需要创建新线程的线程池

内部采用SynchronousQueue存储等待的任务,这个阻塞队列不存储工作线程,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程,比较适合处理执行时间比较小的任务

 public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());//不存储的阻塞队列}

CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为Integer.MAX_VALUE,即maximumPool是“无界“”的。keepAliveTime设置为60L,意味着CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。FixedThreadPool和SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列。CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,CachedThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。

4、通过Executors.newScheduledThreadPool() 延迟运行任务或者定期执行任务的线程池 (调度池)

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,内部使用DelayQueue作为容器。它主要用来在给定的延迟之后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor的功能与Timer类似,但ScheduledThreadPoolExecutor功能更强大、更灵活。一般Timer对应的是单个后台线程,而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。在实际的业务场景中可以使用该线程池定期的同步数据。

public class Executors {public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);}...}public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());}...
}

调度池内部使用的DelayQueue是一个无界队列,所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor中没有什么意义(设置maximumPoolSize的大小没有什么效果)。

三、自定义线程池

在自定义线程池之前再简单总结下线程的创建的一般规则: ThreadPoolExecutor对象初始化时,不创建任何执行线程,当有新任务进来时,才会创建执行线程。构造ThreadPoolExecutor对象时,需要配置该对象的核心线程池大小和最大线程池大小

  • 当目前执行线程的总数小于核心线程大小时,所有新加入的任务,都在新线程中处理。
  • 当目前执行线程的总数大于或等于核心线程时,所有新加入的任务,都放入任务缓存队列中。
  • 当目前执行线程的总数大于或等于核心线程,并且缓存队列已满,同时此时线程总数小于线程池的最大大小,那么创建新线程,加入线程池中,协助处理新的任务。
  • 当所有线程都在执行,线程池大小已经达到上限,并且缓存队列已满时,就rejectHandler拒绝新的任务。

为了更加明确线程池的运行规则和规避耗尽资源的风险,尽量不要用Executors 去创建线程池。

    static int NUMBER_OF_CORES=Runtime.getRuntime().availableProcessors();static int KEEP_ALIVE_TIME=1;static TimeUnit KEEP_ALIVE__UNIT=TimeUnit.SECONDS;static BlockingQueue<Runnable> taskQueue=new LinkedBlockingDeque<>();ExecutorService mExecutorService=new ThreadPoolExecutor(NUMBER_OF_CORES,NUMBER_OF_CORES*2,KEEP_ALIVE_TIME,KEEP_ALIVE__UNIT,taskQueue,new BackgroundThreadFactory(),new DefaultRejectExecutionHandler())

四、线程池的合理配置

要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。

  • 任务的性质——CPU密集型任务、IO密集型任务和混合型任务。
  • 任务的优先级——高、中和低。
  • 任务的执行时间——长、中和短。
  • 任务的依赖性——是否依赖其他系统资源,如数据库连接。

性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。可以通过
Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先执行。

注意:如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。

执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行。
依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则CPU空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用CPU。建议使用有界队列。有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点儿,比如几千。有一次,我们系统里后台任务线程池的队列和线程池全满了,不断抛出抛弃任务的异常,通过排查发现是数据库出现了问题,导致执行SQL变得非常缓慢,因为后台任务线程池里的任务全是需要向数据库查询和插入数据的,所以导致线程池里的工作线程全部阻塞,任务积压在线程池里。如果当时我们设置成无界队列,那么线程池的队列就会越来越多,有可能会撑满内存,导致整个系统不可用,而不只是后台任务出现问题。当然,我们的系统所有的任务是用单独的服务器部署的,我们使用不同规模的线程池完成不同类型的任务,但是出现这样问题时也会影响到其他任务。

Java 进阶——多线程优化之线程池 ThreadPoolExecutor的使用(三)相关推荐

  1. java executor 源码_Java线程池ThreadPoolExecutor深度探索及源码解析

    我们的程序里,时常要使用多线程.因此多线程的管理变的尤为重要.ThreadPoolExecutor很好的解决了这一点.本篇文章主要从源码入手,分析ThreadPoolExecutor的原理. 1.标记 ...

  2. Java 并发编程之自定义线程池 ThreadPoolExecutor

    1)定义一个任务线程 public class Task implements Runnable {private String name;Task(String name) {this.name = ...

  3. 线程优化-使用线程池ThreadPoolExecutor

    我们在进行异步处理时,一般做法是new Thread(),但会存在以下问题: 过多的线程不方便统一管理 大量线程占用系统资源 每次创建线程影响性能 采用线程池能够控制线程并发数 实现单线程顺序执行 重 ...

  4. Python 线程池 ThreadPoolExecutor(一) - Python零基础入门教程

    目录 一.Python 线程池前言 二.Python 线程池原理 三.Python 线程池 ThreadPoolExecutor 函数介绍 四.Python 线程池 ThreadPoolExecuto ...

  5. 【Java进阶】Java并发类库提供的线程池有哪几种? 分别有什么特点?

    我在专栏第 17 讲中介绍过线程是不能够重复启动的,创建或销毁线程存在一定的开销,所以利用线程池技术来提高系统资源利用效率,并简化线程管理,已经是非常成熟的选择. 今天我要问你的问题是,Java 并发 ...

  6. Java多线程-新特性-线程池

    Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定可靠的多线程程序 ...

  7. Java线程池ThreadPoolExecutor的实例

    Java.util中的线程池和Spring框架对这个类的扩展 1.单独通过java里的ThreadPoolExecutor这个类,可以创建线程池,如果系统采用Spring框架设计,可以采用Thread ...

  8. JAVA 多线程 JAVA 如何开发一个自定义线程池

    1.多线程设计介绍 每一个线程的启动和结束都是比较消耗时间和占用资源的. 如果在系统中用到了很多的线程,大量的启动和结束动作会导致系统的性能变卡,响应变慢. 为了解决这个问题,引入线程池这种设计思想. ...

  9. java多线程编程之线程池技术全面解读

    在多线程编程时,创建线程是十分消耗资源的,当线程创建过多时,便会引发内存溢出,因此引入了线程池技术. 目录 线程池的优势 线程池的创建&使用 线程池的工作原理 线程池的参数 功能线程池 线程池 ...

最新文章

  1. Galgame研发日志:预算爆炸,问题不大
  2. 博为峰Java技术文章 ——JavaSE Swing JPanel III
  3. JPA / Hibernate实体状态转换的初学者指南
  4. 如何清理和维护计算机,电脑的优化与维护操作教程
  5. linux shell 逻辑运算符、逻辑表达式
  6. 灯珠电路图_可充电led台灯电路图
  7. vs2008 64位 如何连接64位oracle,vs2008中如何连接服务器上Oracle实例?
  8. 设计模式-职责链模式 (分离职责,动态组合)
  9. 再见,Python!你好,Go语言\n\n
  10. MySQL8.0.22解压安装教程
  11. Linux 应急响应入门:入侵排查应该这样做
  12. 【无线安全】Kali 暴力破解 WiFi 密码步骤详解
  13. 市政管理学(试题及答案)汇总
  14. No content to map to Object due to end of input
  15. CDN 网站部署全站加速服务
  16. 电视机hdr是什么功能
  17. 不做教书匠,要做研究型教师
  18. 小程序嵌套h5页面_快速小程序开发之微信小程序内嵌 H5
  19. RT-Thread 软件包制作及发布流程
  20. 记录一个有关QT生成PDF的过程

热门文章

  1. ubuntu 添加用户到用户组
  2. 抢抓东数西算落地:英特尔聚焦计算与能耗有效性,全面优化数据中心资源配置...
  3. “所爱隔山海,山海犹可平”130名老师的AI版长安十二时辰
  4. php自动加反斜杠,处理php自动反斜杠的函数代码
  5. 七条建议:用Stata处理文字变量和字符变量
  6. 南理工计算机专业录取分数线,2020南京理工大学录取分数线_历年各专业分数线(2017-2019)_各省投档线_一品高考网...
  7. MySql数据库与JBDC笔记
  8. linux如何正则匹配删除一行,shell sed命令匹配替换删除最后第一行字符正则表
  9. maven打包jar到本地仓库
  10. 实验1-顺序结构:7-1 计算整数各位数字之和(10分)