深入了解Java线程池的原理使用及性能优化
作者:彷徨的蜗牛
1、什么是线程及线程池
线程是操作系统进行时序调度的基本单元。
线程池可以理解为一个存在线程的池子,就是一个容器,这个容器只能存在线程。这个容器有大小,可以放7,8个,也可以放3,4个。也可以把容器装满,但是都有一个最大值,比如说12个。比如说我这边线程池一般都装5个线程,最多能装12个。这个时候有五个人需要使用线程池,他就拿走了5个线程,然后在来两个人怎么办,他肯定没有线程可以使用,他必须等着那5个人使用完才行。但是我的池子能装12个,我只有5个线程怎么行,我肯定还得在在装几个线程,要不然人再多一点就不够了,这时候来了2个,我在生产2个线程,总数到7个,这个时候剩下2个人就不需要等待了,就可以直接使用。如果在来6个人呢,这个时候,我的池子里面可能只剩下5个线程的容量了,我能在生产5个线程但是,还有一个人得在哪等着才行。我也不能让人家漫无目的的等着啊,我找5个凳子吧,你们坐那等着,然后第一波五个人用完线程结束了,一下子腾出来了5个线程,剩下的一个人可以使用线程,这个时候依次又来了10个人,我的线程只有4个人可以使用,位置能坐五个人,剩下一个人怎么办,要不直接拒绝,我这边没有位,你要不先去别的地方看看,但是直接拒绝肯定很让人心里不舒服,我得在想几种拒绝策略。。。,我看我的线程池用的人还比较多,这么多人用,要是有人一直占着我的线程池怎么办,肯定得想个办法处理?要不就直接一个线程只能使用1分钟,使用完之后立刻回收,如果想在使用,重新排队等待。这样我的线程生意越做越好,只要有人用,他就一直跑。
是不是有点像饭店或者是自助餐店,自助餐店是比较形象的,我的饭店里面只要有位置就可以坐人,达到最大的量,剩下的客户只能在门口等待了,饭店里面的客户走一个,来一个在外边等待的,如果等待的位置没有了,客户看看没位置了就直接走了,如果有的人特别想吃,就在哪多等一会。在饭店里面的客户吃的时间也不能太长(一般在没有位置的情况下),大概2个小时,吃完就要离开。
根据以上我的描述,大概可以确定线程池里面有什么?
装了多少个线程、能装多少线程、线程可以保留多长时间、线程等待区、如何拒绝、创建线程
1.1、为什么要使用线程
程序的运行必须依靠进程,进程的实际执行单元就是线程。
- 系统内服务的调用。系统是依托于进程运行的,系统内有很多服务,服务之间有交互,服务的运行依托于线程运行。多服务运行依托于多线程运行。服务之间的调用及数据交换是依托于进程间的内存进行数据交互的,同时线程也可以构建自己的内存空间。依托于进程间的资源调度和数据交互。
- 多线程可以提高程序的执行性能。例如,有个 90 平方的房子,一个人打扫需要花费 30 分钟,三个人打扫就只需要 10 分钟,这三个人就是程序中的“多线程”。
在很多程序中,需要多个线程互相同步或互斥的并行完成工作。
线程相比进程来说,更加的轻量,所以线程的创建和销毁的代价变得更小。
线程提高了性能,虽然线程宏观上是并行的,但微观上却是串行。从CPU角度线程并无法提升性能,但如果某些线程涉及到等待资源(比如IO,等待输入)时,多线程允许进程中的其它线程继续执行而不是整个进程被阻塞,因此提高了CPU的利用率,从这个角度会提升性能。
在多CPU或多核的情况下,使用线程不仅仅在宏观上并行,在微观上也是并行的。
1.2、为什么要使用线程池
多线程可以提高程序的执行性能
- 比如说吃自助餐,当餐位足够多的时候,人也足够多的时候,自助餐盈利也是最多了,同时也可以提供就餐率与客户满意度。如果有200个人吃饭,有一百个餐位的话,每个人平均吃1个小时,那200个人吃两个小时就吃完了。如果只有10个餐位的话,200个人需要吃20个小时左右,想一下如果剩下的在哪里焦急等待吃饭的客户心里多么的不开心。
- 自助餐餐的餐位就是线程,当线程足够多的时候就可以满足更多的人吃饭,但是也不是说线程越多越好,毕竟不是一定每次都会有200个客户过来吃饭,就算有200个客户过来吃饭,也需要评估一下,饭店里面的厨师够不够,打扫卫生的阿姨能不能收拾过来,饭店里面的盘子够不够等基本的硬件因素。这个就相当于系统的配置一下,需要一些本质上的内存、CPU处理等一些硬件条件。
创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率(只要线程一直执行就不会销毁)
- 记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3,如果T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!正好,线程池缓存线程,可用已有的闲置线程来执行新任务,避免了T1+T3带来的系统开销,当然一直存活的核心线程也会消耗CPU资源
线程并发数量过多,抢占系统资源从而导致阻塞
- 我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况,运用线程池能有效的控制线程最大并发数,避免以上的问题
对线程进行一些简单的管理
- 比如:延时执行、定时循环执行的策略等运用线程池都能进行很好的实现
1.3、线程池的优点
提高线程利用率
- 保证存在业务是的时候使用,不存在业务的时候就释放掉,合理使用线程,避免资源浪费
提高程序的响应速度
- 由线程池统一管理的话,资源分配使用统一的调度池进行调度,出现使用线程的情况能避免线程的创建及销毁的耗时,可以直接使用线程。
便于统一管理线程对象
- 线程池可以保证线程的统一调配与管理。
可以控制最大并发数
- 服务器是有线程使用上限的,线程使用对资源也有很大的消耗,所以线程池能很好的控制线程资源,避免浪费。
2、线程池在java中的使用
ThreadPoolExecutor这个类是java中的线程池类,可以使用它进行线程的池化。
// 根据上面的描述大概分析一下线程都需要什么及参数的解析 // corePoolSize 核心线程数,就是上面说的装了多少个线程 // maximumPoolSize 最大线程数,就是上面说的能装多少线程 // keepAliveTime 存活时间,就是上面说的线程可以保留多长时间 // TimeUnit 这个是时间单位,有时、分、秒、天等等,是存活时间的单位 // BlockingQueue<Runnable> 这是一个等待队列,就是上面显示的线程等待区 // ThreadFactory 线程工厂,就是上面描述的如何创建线程,由谁创建 // RejectedExecutionHandler 拒绝策略,就是上面显示的如何拒绝,是直接拒绝还是婉拒 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue) public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
可以看到,其需要如下几个参数:
- corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
- maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
- keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
- unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
- workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
- threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
- handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
2.1、线程池的工作原理
2.2、线程池的java代码示例
import java.util.concurrent.*; public class ThreadTest { public static void main(String[] args) { ExecutorService threadPoolExecutor = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory()); for (int i = 0; i < 20; i++) { int finalI = i; threadPoolExecutor.submit( ()->{ System.out.println(Thread.currentThread().getName() + "========" + finalI); }); } threadPoolExecutor.shutdown(); } }
执行结果:
pool-1-thread-1========0
pool-1-thread-3========2
pool-1-thread-3========4
pool-1-thread-2========1
pool-1-thread-3========5
pool-1-thread-2========8
pool-1-thread-5========7
pool-1-thread-1========3
pool-1-thread-4========6
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@61e717c2 rejected from java.util.concurrent.ThreadPoolExecutor@66cd51c3[Running, pool size = 5, active threads = 2, queued tasks = 0, completed tasks = 7]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.halo.communication.ThreadTest.main(ThreadTest.java:10)
执行的线程数超过了线程池可容纳的大小,线程池使用默认拒绝策略拒绝多余线程执行,然后开始出现异常处理。上面执行的线程数到thread-5,5是线程池的默认最大线程数。然后执行for循环20次,进行执行到8的时候出现异常,说明线程池已经超载满负荷执行,所以线程池执行拒绝策略。
到此这篇关于深入了解Java线程池的原理使用及性能优化的文章就介绍到这了,更多相关Java线程池内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!