Java 并发编程(线程池篇)
前言
你是否曾经遇到过这样的情况:当你在开发一个多线程应用程序时,你需要同时创建许多线程来完成不同的任务,但是你很快就发现,线程数量太多会导致程序的性能下降,甚至会导致程序崩溃。这时,线程池就像一个救世主一样出现了!它可以帮助我们更好地管理和控制线程的数量,从而提高程序的性能和稳定性。如果你想要了解如何使用线程池来解决这个问题,那么你来对地方了!好了废话不多说,咱们开始吧!!!
关于线程池
什么是池?关于池化思想?
线程池是一种多线程的一种处理形式,处理过程中可以将任务添加到队列中,然后创建线程后自动启动这些任务。
池化思想: 比如:线程池、字符串常量池、数据库连接池
线程池的优势
降低资源消耗
:通过重复利用已创建的线程,降低创建线程和销毁的开销。提高响应速度
:当有任务时,不需要创建线程,直接拿池中的线程对象执行。提高线程的可管理性
: 在系统中,线程是稀缺的资源,如果一味创建和销毁线程,不仅会消耗系统资源,而且还降低了系统的稳定性;使用线程的话,可以将线程对象同意分配、调优和监控。
如何构造一个线程池
关于线程池ThreadPoolExecutor
查看源码发现它继承了AbstractExecutorService
抽象类,而AbstractExecutorService
抽象类实现了ExecutorService
接口ExecutorService
接口又继承了 Executor
接口。所以ThreadPoolExecutor
间接实现了ExecutorService
接口和Executor
接口。关系图如下:
线程池的使用
线程池的真正的实现类(也是线程池的标准类)是:ThreadPoolExecutor 类,下边是所有的构造方法:
线程池的参数:
corePoolSize(必要)
:代表核心线程数,默认的情况下核心线程数一直存活,当 allowCoreThreadTimeout 设置为 true,核心线程池也会被超时回收。maximumPoolSize(必要)
:代表最大线程数,是核心线程数和非核心线程数之和。keepAliveTime(必要)
:代表线程闲置超时时长,如果超过该时长,非核心线程会被回收。当 allowCoreThreadTimeout 设置为 true,核心线程池也会被超时回收。unit(必要)
:指定 keepAliveTime 参数的时间单位。workQueue(必要)
:任务队列,通过 submit 方法将任务注册到该队列中。threadFactory(选择)
:线程工厂,用于指定为线程池创建新线程的方式。handler(选择)
:拒绝策略,当前线程池达到线程饱和(最大线程数 + 任务队列数)的拒绝策略。
线程池的工作原理
下图是描述线程池工作的原理,同时对上面的参数有一个更深的了解。
线程池把任务的提交和任务的执分开了,当一个任务被提交到线程池之后:
如果此时线程数
小于
核心线程数,那么就会新起一个线程来执行当前的任务。如果此时线程数
大于
核心线程数,那么就会将任务塞入阻塞队列中,等待被执行。如果阻塞队列满了,并且此时线程数
小于
最大线程数,那么会创建新线程来执行当前任务。如果阻塞队列满了,并且此时线程数
大于
最大线程数,那么会采取拒绝策略。
--
以上就是任务提交给线程池后各种状况汇总,一个很容易出现理解错误的地方就是当线程数达到核心数的时候,任务是先入队,而不是先创建最大线程数。从上述可知,线程池里的线程不是一开始就直接拉满的,是根据任务量开始慢慢增多的,这就算一种懒加载,到用的时候再创建线程,节省资源。
问答时间
当前线程数小于核心线程数,并且线程都处于空闲状态,现在提交一个任务,是新启一个线程还是使用之前创建的线程?
此时线程池会新启一个线程来执行这个新任务,不管之前的线程是否空闲。
如何理解核心线程数?
虽然说线程池是延迟创建线程,但实际是想要快速获得核心线程数的线程。核心线程本质上是线程池需要这些数量的线程来处理任务的,而最大线程数是为了应付突发情况。
举个栗子:正常情况下施工队只要 5 个人去干活,这 5 人其实就是核心线程,但是由于工头接的活太多了,导致 5 个人在约定工期内干不完,所以工头又去找了 2 个人来一起干,所以 5 是核心线程数,7 是最大线程数。平时就是 5 个人干活,特别忙的时候就找 7 个,等闲下来就会把多余的 2 个辞了。不论是核心还是非核心线程,在线程池里面都是一视同仁,当淘汰的时候不会管是哪些线程,反正留下核心线程数个线程即可。
线程池有几种状态?
RUNNING:能接受新任务,并处理阻塞队列中的任务
SHUTDOWN:不接受新任务,但是可以处理阻塞队列中的任务
STOP:不接受新任务,并且不处理阻塞队列中的任务,并且还打断正在运行任务的线程,就是直接撂担子不干了!
TIDYING:所有任务都终止,并且工作线程也为 0,处于关闭之前的状态
TERMINATED:已关闭。
线程池的生命周期是怎样的?
线程池有哪些工作队列?
ThreadPoolExecutor 的工作队列可以分为两类:无界队列和有界队列
ArrayBlockingQueue
是基于一个数组结构的有界阻塞队列,该队列按照先进先出(FIFO)原则对元素进行排序。
LinkedBlockingQueue
是基于链表结构的阻塞队列,队列按照先进先出(FIFO)排序元素,吞吐量要高于 ArrayBlockingQueue。
SynchronousQueue
不存储元素的阻塞队列。每个插⼊操作必须等到另⼀个线程调⽤移除操作,否则插⼊操作⼀直处于阻塞状态。
PriorityBlockingQueue
具有优先级的⽆限阻塞队列。
DelayQueue
是任务定时周期的延迟执⾏的队列。根据指定的执⾏时间从⼩到⼤排序,否则根据插⼊到队列的先后排序。
几种常见的线程池使用场景
newSingleThreadExecutor
创建⼀个单线程化的线程池,它只会⽤唯⼀的⼯作线程来执⾏任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执⾏。
newFixedThreadPool
创建⼀个定长线程池,可控制线程最⼤并发数,超出的线程会在队列中等待。
newCachedThreadPool
创建⼀个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若⽆可回收,则新建线程。
newScheduledThreadPool
创建⼀个定长线程池,⽀持定时及周期性任务执⾏。
newWorkStealingPool
⼀个拥有多个任务队列的线程池,可以减少连接数,创建当前可⽤ cpu 数量的线程来并⾏执⾏。
shutdown 和 shutdownNow 区别
shutdown 和 shutdownNow 均可⽤于关闭线程池.
shutdown
当我们调⽤ shutdown ⽅法后,线程池将不再接受新的任务,但也不会去强制终⽌已经提交或者正在执⾏中的任务。
shutdownNow
当调⽤ shutdownNow ⽅法后,会向正在执⾏的全部任务发出 interrupt() 停⽌执⾏信号,对还未开始执⾏的任务全部取消,并且返回还没开始的任务列表。
评论