本篇内容介绍了“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的时候出现异常,说明线程池已经超载满负荷执行,所以线程池执行拒绝策略。