文章目录
- 为什么需要线程池?
- 池化思想
- 常用方法
- execute()方法
- submit()方法
- shutdown
- isShutdown
- isTerminated
- awaitTermination
- shutdownNow
- 创建线程池 七个参数
- 流程
- JAVA线程池有哪几种类型?
- 线程池常用的阻塞队列有哪些?
- 源码中线程池是怎么复用线程的?
- 如何合理配置线程池参数?
————————————————————————————————
为什么需要线程池?
线程每次执行完之后会销毁,然后创建,新建线程
获取一个任务,销毁线程–标记为无效空间,然后在新建线程,再销毁线程(每个任务之间都有销毁和新建)
好处就是不会一直占据着空间和CPU
坏处就是需要一直新建销毁 浪费时间这样的时间的开销很大,
如何防止线程被销毁呢,线程设置死循环,不让线程执行完毕,这样线程就不会被销毁掉了
线程一直存活的话,如果一个任务运行完成可以直接执行下一个任务
流程:一直获取任务,不需要新建与销毁
缺点是:一直消耗着cpu 同时一直占着空间
池化思想
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统-分配、调优和监控
常用方法
execute()方法
用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否:
只能接收Runnable类型的参数,而Runnable类型的任务不可以返回执行结果
submit()方法
submit()方法可以接收Callable、Runnable两种类型的参数,Callable类型的任务作为Runnable的一种补充,允许有返回值,允许抛出异常
submit()方法自身并不会传递结果,而是返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get(方法来获取返回值,get()方法会阻塞当前线程直到任务完成,调用Future.get()方法获取执行结果时,可以捕获异步执行过程中抛出的受检异常和运行时异常,并进行对应的业务处理。
shutdown
shutdown(),它可以安全地关闭一个线程池,调用 shutdown() 方法之后线程池并不是立刻就被关闭,因为这时线程池中可能还有很多任务正在被执行,或是任务队列中有大量正在等待被执行的任务,调用 shutdown() 方法后线程池会在执行完正在执行的任务和队列中等待的任务后才彻底关闭。
调用 shutdown() 方法后如果还有新的任务被提交,线程池则会根据拒绝策略直接拒绝后续新提交的任务。
isShutdown
第二个方法叫作 isShutdown(),它可以返回 true 或者 false 来判断线程池是否已经开始了关闭工作,也就是是否执行了 shutdown 或者 shutdownNow 方法。
这里需要注意,如果调用 isShutdown() 方法的返回的结果为 true 并不代表线程池此时已经彻底关闭了,这仅仅代表线程池开始了关闭的流程,也就是说,此时可能线程池中依然有线程在执行任务,队列里也可能有等待被执行的任务
isTerminated
这个方法可以检测线程池是否真正“终结”了,这不仅代表线程池已关闭,同时代表线程池中的所有任务都已经都执行完毕了。
比如我们上面提到的情况,如果此时已经调用了 shutdown 方法,但是还有任务没有执行完,那么此时调用 isShutdown 方法返回的是 true,而 isTerminated 方法则会返回 false。
直到所有任务都执行完毕了,调用 isTerminated() 方法才会返回 true,这表示线程池已关闭并且线程池内部是空的,所有剩余的任务都执行完毕了。
awaitTermination
主要用来判断线程池状态的。
比如我们给 awaitTermination 方法传入的参数是 10 秒,那么它就会陷入 10 秒钟的等待,直到发生以下三种情况之一:
- 等待期间(包括进入等待状态之前)线程池已关闭并且所有已提交的任务(包括正在执行的和队列中等待的)都执行完毕,相当于线程池已经“终结”了,方法便会返回 true
- 等待超时时间到后,第一种线程池“终结”的情况始终未发生,方法返回 false
- 等待期间线程被中断,方法会抛出 InterruptedException 异常
调用 awaitTermination 方法后当前线程会尝试等待一段指定的时间,如果在等待时间内,线程池已关闭并且内部的任务都执行完毕了,也就是说线程池真正“终结”了,那么方法就返回 true,否则超时返回 fasle。
shutdownNow
shutdownNow(),它和 shutdown() 的区别就是多了一个 Now,表示立刻关闭的意思,不推荐使用这一种方式关闭线程池。
在执行 shutdownNow 方法之后,首先会给所有线程池中的线程发送 interrupt 中断信号,尝试中断这些任务的执行,然后会将任务队列中正在等待的所有任务转移到一个 List 中并返回,我们可以根据返回的任务 List 来进行一些补救的操作,例如记录在案并在后期重试。
shutdown 没有返回值,而 shutdownNow 会返回关闭前任务队列中未执行的任务集合(List)
创建线程池 七个参数
1、核心线程数,就是三个常用柜台
2、最大线程数,也就是最多五个窗口
3、存活时间,多久没有任务后线程就会停止
4、时间单位,
5、等待队列,三个等候区
6、线程工厂
7、 拒绝策略
流程
1、execute/submit方法向线程池添加任务,只要核心线程数没满,每次过来一个新任务,都会创建一个新线程
2.当任务大于核心线程数corePoolSize,就向阻塞队列添加任务。
3.如果阻塞队列已满,需要通过比较参数maximumPoolSize(最大线程数的作用就是加速消耗),在线程池创建新的线程,当线程数量大于maximumPoolSize(最大线程数),传输速度仍然大于所有线程执行任务的速度,说明当前设置线程池中线程已经处理不了了,就会执行拒绝策略。
但一段时间内没有提交任务,所有线程都会再次停止,然后重新开启那10个核心线程,因为如果不设置提交时间的话,一直开着这么多的线程对CPU来说是巨大的消耗
队列长度一定要是有界限的,这样才能触发最大线程数,加速任务的执行速度
JAVA线程池有哪几种类型?
1、ScheduledThreadPool:-------------DelayedWorkQueue
创建一个定长线程池,支持定时或周期性执行任务。比如每隔 10 秒钟执行一次任务
2、FixedThreadPool:可重用固定线程数的线程池-----------------LinkedBlockingQueue
它的核心线程数和最大线程数是一样的,所以可以把它看作是固定线程数的线程池,它的特点是线程池中的线程数除了初始阶段需要从 0 开始增加外,之后的线程数量就是固定的,就算任务数超过线程数,线程池也不会再创建更多的线程来处理任务,而是会把超出线程处理能力的任务放到任务队列中进行等待。而且就算任务队列满了,到了本该继续增加线程数的时候,由于它的最大线程数和核心线程数是一样的,所以也无法再增加新的线程了。
3、CachedThreadPool:可缓存的线程池 ----------SynchronousQueue
创建一个可缓存线程池,线程数是几乎可以无限增加的,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
有一个用于存储提交任务的队列,但这个队列是 SynchronousQueue,队列的容量为0,实际不存储任何任务,它只负责对任务进行中转和传递,所以效率比较高。当我们提交一个任务后,线程池会判断已创建的线程中是否有空闲线程,如果有空闲线程则将任务直接指派给空闲线程,如果没有空闲线程,则新建线程去执行任务,这样就做到了动态地新增线程
4、SingleThreadExecutor:只有一个线程的线程池 ----------LinkedBlockingQueue
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 如果线程在执行任务的过程中发生异常,线程池也会重新创建一个线程来执行后续的任务。这种线程池由于只有一个线程,所以非常适合用于所有任务都需要按被提交的顺序依次执行的场景
5、threadPool = new ThreadPoolExecutor();
//默认线程池,可控制参数比较多
线程池常用的阻塞队列有哪些?
- LinkedBlockingQueue
LinkedBlockingQueue这样一个没有 容量限制的阻塞队列来存放任务。
这里需要注意,由于线程池的任务队列永远不会放满,所以线程池只会创建核心线程数量的线程,所以此时的最大线程数对线程池来说没有意义 - SynchronousQueue
- DelayedWorkQueue
第三种阻塞队列是DelayedWorkQueue,这两种线程池的最大特点就是可以延迟执行任务,比如说一定
时间后执行任务或是每隔-定的时间执行一次任务。
源码中线程池是怎么复用线程的?
源码中ThreadPoolExecutor中有个内置对象Worker,每个worker都是一个线程, worker线程数量和参数有关,每个worker 会while死循环从阻塞队列中取数据,通过置换worker中Runnable对象,运行其run方法起到线程置换的效果,这样做的好处是避免多线程频繁线程切换,提高程序运行性能。
如何合理配置线程池参数?
自定义线程池就需要我们自己配置最大线程数maximumPoolSize,为了高效的并发运行,这时需要看我们的业务是IO密集型还是CPU密集型。
CPU密集型
CPU密集的意思是该任务需要最大的运算,而没有阻塞,CPU-直全速运行。CPU密集任务只有在真正的多核CPU上才能得到加速(通过多线程)。而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那么多。
I0密集型
I0密集型,即该任务需要大量的I0,即大量的阻塞。在单线程上运行I0密集型的任务会导致大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上这种加速主要就是利用了被浪费掉的阻塞时间。
10密集型时,大部分线程都阻塞,故需要多配制线程数。公式为:
CPU核数*2
CPU核数/(1-阻塞系数)阻塞系数在0.8~0.9之间
查看CPU核数:
Sys tem. out . print1n(Runtime . getRuntime () . availableProcessorsO);