一、线程池

线程池适合处理的任务:执行时间短、工作内容较为单一。

合理使用线程池带来的好处:

1)降低资源消耗:重复利用已创建的线程降低线程创建和销毁造成的开销

2)提高响应速度:当任务到达时,任务可以不用等待线程创建就能立即执行

3)提高线程的可管理性:可以统一对线程进行分配、调优和监控

4)提供更多强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池SchedulerThreadPoolExecutor,允许任务延期执行或定期执行

线程池使应用能更加充分利用CPU、内存、网络、IO等系统资源。线程的创建需要开辟虚拟机栈、本地方法栈、程序计数器等线程私有的内存空间。

在线程销毁时需要回收这些系统资源。因此频繁的创建和销毁线程会浪费大量的系统资源,增加并发编程风险。另外,在服务器负载过大的时候,如何让新的线程等待或者友好地拒绝服务?这些都是线程本身无法解决的。所以需要通过线程池协调多个线程,并实现类似主次线程隔离、定时执行、周期执行等任务。线程池的作用包括:

1):利用线程池管理并复用线程、控制最大并发数等

2):实现任务线程队列缓存策略和拒绝机制

3):实现某些与时间相关的功能,如定时执行、周期执行

4):隔离线程环境。通过配置两个或多个线程池,将一台服务器上较慢的服务和其他服务隔离开,避免各服务线程相互影响。

ThreadPoolExecutor

UML类图:

ThreadPoolExecutor实现的顶层接口是Executor,顶层接口Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。

ExecutorService接口增加了一些能力:

(1)扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;

(2)提供了管控线程池的方法,比如停止线程池的运行。

AbstractExecutorService则是上层的抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。

最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。

主要参数:

1)corePoolSize  线程池核心线程的大小

2)maximumPoolSize  线程池最大线程的大小

3)keepAliveTime  空闲线程的存活时间

4)BlockingQueue  用来暂时保存任务的工作队列

5)RejectedExecutionHandler  线程池已经关闭或者饱和(达到了最大线程数且工作队列已满),executor()方法将调用Handler

参数详细说明:

1、corePoolSize  表示常驻核心线程数,如果大于0,则即使执行完任务,线程也不会被销毁(allowCoreThreadTimeOut为false)。因此这个值的设置非常关键,设置过小会导致线程频繁地创建和销毁,设置过大会造成浪费资源

2、maximumPoolSize  表示线程池能够容纳的最大线程数。必须大于或者等于1。

3、keepAliveTime  表示线程池中的线程空闲时间,当空闲时间达到keepAliveTime值时,线程会被销毁,避免浪费内存和句柄资源。在默认情况下,当线程池中的线程数大于corePoolSize时,keepAliveTime才起作用,达到空闲时间的线程会被销毁,直到只剩下corePoolSize个线程为止。但是当ThreadPoolExecutor的allowCoreThreadTimeOut设置为true时(默认false),核心线程超时后也会被回收。(一般设置60s)

4、TimeUnit  表示时间单位,keepAliveTime的时间单位通常是TimeUnit.SECONDS

5、BlockingQueue  表示缓存队列。

6、threadFactory  表示线程工厂。它用来生产一组相同任务的线程。线程池的命名是通过给threadFactory增加组名前缀来实现的。在用jstack分析时,就可以知道线程任务是由哪个线程工厂产生的。

7、handler   表示执行拒绝策略的对象。当超过workQueue的缓存上限的时候,就可以通过该策略处理请求,这是一种简单的限流保护。友好的拒绝策略可以是如下

三种:

1):保存到数据库进行削峰填谷。在空闲时再取出来执行

2):转向某个提示页面

3):打印日志

线程池处理任务规则及流程说明:

首先,所有的任务都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来的执行流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。

其执行过程如下:

1、首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。

2、如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。

3、如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。

4、如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。

5、如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

6、线程池中存活的线程执行完当前任务后,会在循环中反复从BlockingQueue队列中获取任务来执行

总结使用线程池需要注意以下几点:

1、合理设置各类参数,应根据实际业务场景来设置合理的工作线程数

2、线程资源必须通过线程池提供,不允许在应用中自行显式创建线程

3、创建线程或线程池请指定有意义的线程名称,方便出错时回溯

4、线程池不允许使用Executors,而是通过ThreadPoolExecutor的方式来创建,这样的处理方式能更加明确线程池的运行规则,规避资源耗尽的风险。

另外,线程池中的线程数量不是越多越好,具体的数量需要评估每个任务的处理时间,以及当前计算机的处理器能力和数量。使用的线程过少,无法发挥处理器的性能;使用的线程过多,将会增加上下文切换的开销,反而起到相反的作用。

合理配置线程池

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

1)任务的性质:CPU密集型任务(处理任务不需要依赖外部 I/O),IO密集型任务(计算占比少,磁盘IO,网络IO占比多)或混合型任务

2)任务的优先级:高、中、低

3)任务的执行时间:长、中、短

4)任务的依赖性:是否依赖其他系统资源,如数据库连接,接口调用等

策略:

性质不同的任务(CPU密集型、IO密集型、速度优先、吞吐量优先)可以用不同规模的线程池分开处理,这样可以更好确定线程数量。

若CPU的核心数为N:

1)CPU密集型任务应配置尽可能少的线程,如配置核心数为 N+1 个线程的线程池。

2)由于IO密集型的任务并不是一直都在执行任务,存在读写阻塞等待,因此应配置较多的线程,如核心数2*N 个,最大线程数 25*N个

3)混合型的任务,如果可以拆分,将其拆分为一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行的时间相差太大,则没必要进行分解。

4)优先级不同的任务可以使用优先级队列 PriorityBlockingQueue来处理,它可以让优先级高的任务先执行。

5)依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则CPU空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用CPU。

6)建议使用有界队列,并可以根据需要设置大一点。基于链表的LinkedBlockingQueue比基于数组的ArrayBlockingQueue吞吐量高,因为增删多,需要内存大。

有界队列能增加系统的稳定性和预警能力,可以根据需要设置大一点,比如几千。有时会遇到任务执行缓慢的时候,为了避免任务被拒绝,可以适当将队列设置大一些。

7)大量的耗时任务执行的时候,并不需要瞬时的完成,而是关注如何使用有限的资源,尽可能在单位时间内处理更多的任务,也就是吞吐量优先。这个时候应该设置较大队列去缓冲并发任务,调整合适的核心线程数,避免过多引发 线程上下文切换频繁导致的资源消耗(会降低处理任务的速度,降低吞吐量)

参数动态化

ThreadPoolExecutor 提供了API供ThreadPoolExecutor的实例来动态设置线程池的参数。

以setCorePoolSize为例,在运行期线程池使用方调用此方法设置corePoolSize之后,线程池会直接覆盖原来的corePoolSize值,并且基于当前值和原始值的比较结果采取不同的处理策略。对于当前值小于当前工作线程数的情况,说明有多余的worker线程,此时会向当前idle的worker线程发起中断请求以实现回收,多余的worker在下次idle的时候也会被回收;对于当前值大于原始值且当前队列中有待执行任务,则线程池会创建新的worker线程来执行队列任务。

结合ZooKeeper分布式配置中心,利用Zookeeper watch机制,注册节点监听,节点变动自动拉取节点配置判断线程池参数是否变化,若变化,则修改线程池参数,实现即时修改,即时生效。

线程池的监控

如果系统中大量使用线程池,则有必要对线程池进行监控,方便出现问题时,根据线程池的使用状况快速定位问题。可以通过线程池提供的方法获取线程池的信息:

getPoolSize():获取当前线程池中线程的数量

getActiveCount():获取大致的活动的线程数(正在执行任务的线程)

getLargestPoolSize():获取largestPoolSize属性,线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。

getTaskCount():返回已经计划执行的任务的大致总和

getCompletedTaskCount():返回已完成执行的任务的大致总数,小于或等于 getTaskCount()

也可通过直接调用 toString()方法获取大部分信息,当前线程池中线程的数量,活动的即正在执行任务的线程数量,队列大小,已经执行完的任务等。输出结果:

java.util.concurrent.ThreadPoolExecutor@379aea30[Running, pool size = 50, active threads = 50, queued tasks = 300, completed tasks = 1987247]

创建线程池例子,拒绝策略为打印日志并由JVM重启一个线程执行被拒绝的任务:

/*** 创建一个用于发送邮件的线程池,核心线程数为1,最大线程数为5,线程空闲时间为60s,拒绝策略为打印日志并直接执行被拒绝的任务*/

private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(1, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(50), new CustomThreadFactory("redeemSendMail"), newRejectedExecutionHandler() {

@Overridepublic voidrejectedExecution(Runnable r, ThreadPoolExecutor executor) {//打印日志

LOGGER.error("Task:{},rejected from:{}", r.toString(), executor.toString());//直接执行被拒绝的任务,JVM另起线程执行

r.run();

}

});

应用

不同场景下,线程池的参数设置策略不同。

场景1:快速响应请求

追求的是响应速度,所以应该不设置队列去缓冲并发任务,调高corePoolSize和maxPoolSize去尽可能创造多的线程快速执行任务。

场景2:快速处理批量任务

任务量大,不需要瞬时完成,尽可能快执行完成即可。因此关注点在如何使用有限的资源,尽可能在单位时间内处理更多的任务,也就是吞吐量优先的问题。

所以应设置队列去缓冲并发任务,调整合适的corePoolSize去设置处理任务的线程数。在这里,设置的线程数过多可能还会引发线程上下文切换频繁的问题,也会降低处理任务的速度,降低吞吐量。

END

java线程池功能_Java线程池总结相关推荐

  1. java new thread参数_java线程池01-ThreadPoolExecutor构造方法参数的使用规则

    为了更好的使用多线程,JDK提供了线程池供开发人员使用,目的在于减少线程的创建和销毁次数,以此达到线程的重复利用. 其中ThreadPoolExecutor是线程池中最核心的一个类,我们先简单看一下这 ...

  2. java io密集型任务_Java线程池讲解——针对IO密集型任务

    sap java开发技术详解&mdash基础 94.01元 (需用券) 去购买 > 针对 IO 密集型的任务,我们可以针对原本的线程池做一些改造,从而可以提高任务的处理效率. 基本 在阿 ...

  3. java线程不执行_java线程池,阿里为什么不允许使用Executors?

    带着问题 阿里Java代码规范为什么不允许使用Executors快速创建线程池? 下面的代码输出是什么? ThreadPoolExecutor executor = new ThreadPoolExe ...

  4. java线程池执行器_Java线程池ThreadPoolExecutor的使用

    Java线程池ThreadPoolExecutor的使用 ThreadPoolExecutor就是我们用来实现线程的一个执行器,它实现了Excutor和ExecutorService接口.Excuto ...

  5. java 线程池数量_java线程池及创建多少线程合适

    java线程池 1.以下是ThreadPoolExecutor参数完备构造方法: public ThreadPoolExecutor(int corePoolSize,int maximumPoolS ...

  6. java线程池概念_Java 线程池概念、原理、简单实现

    线程池的思想概述 我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结東了,这样频繁创建线程就会大大降低系 ...

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

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

  8. java线程池中断处理_Java线程中断机制

    1. 引言 对Java中断没有一个全面的了解,可能会误以为被中断的线程将立马退出运行,但事实并非如此.中断机制是如何工作的?捕获或检测到中断后,是抛出InterruptedException还是重设中 ...

  9. java线程池分类_JAVA线程池有几种类型?

    常用的JAVA线程池有以下几种类型: 1.newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程. 这种类型的线程池特点是 ...

最新文章

  1. 自定义View合辑(8)-跳跃的小球(贝塞尔曲线)
  2. 几种流行Webservice框架性能对比
  3. java 对象的属性_java-更新对象属性
  4. Unity3D项目实战笔记(10):Unity3D编译IPA的PostEvents–节约时间利器
  5. 肝一波 ~ 手写一个简易版的Mybatis,带你深入领略它的魅力!
  6. python随机划分数据集_Python之机器学习-sklearn生成随机数据
  7. java创建一副牌_用java怎么创建一副扑克牌
  8. SpringMVC教程上篇
  9. VS2010与OpenCV2410简单配置
  10. Spring : @Component注解
  11. Lucene之样例解析(1)-配置Lucene的源代码
  12. Brush、Color、String相互转换
  13. unity android profile adt,Unity Android OBB
  14. php中余数为0,PHP中余数、取余的妙用
  15. java可视化编程教程_JAVA可视化编程——SWING
  16. Eclipse安装SVN插件的方法
  17. 2007年日历带农历表_2007年日历表,2007年农历表(阴历阳历节日对照表)
  18. Flutter Text文本删除线、下划线设置
  19. 熟悉RT-Thread的软硬件环境【RT-Thread学习笔记 1】
  20. 大计基编程题(第十一周)

热门文章

  1. [计算机网络] --- RSTP运行原理及配置
  2. python编程题12-计算并联电阻
  3. 基于Egteks mPower1203仪器和KEYSIGHT N6705仪器的一款智能穿戴产品的低功耗电流测试评估
  4. 3.实现字符串的复制
  5. 如何下载 International Conference on Machine Learning(ICML)顶会的论文?从哪找ICML顶会论文?如何判断会议和期刊级别?
  6. B站(Bilibili) 视频的下载。
  7. SQL优化不会?推荐4 款工具
  8. Linux remote desktop protocol
  9. uCOSIII移植stm32c8t6
  10. windows在此计算机上找不到系统映象,Win7下打开程序提示应用程序或dll 为无效的windows映像怎么办...