Java线程池
2024-01-09 09:37:47
线程池:一个管理线程的池子
1. 为什么使用线程池?
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立刻执行
- 提高线程的可管理性:统一管理线程,避免系统创建大量同类线程而导致消耗完内存
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
2. 线程池执行原理?
创建新的线程需要获取全局锁,通过这种设计可以避免获取全局锁,当ThreadPoolExecutor
完成预热之后(当前运行的线程数大于等于corePoolSize
),提交的大部分任务都会被放到BlockingQueue
为了形象描述线程池的执行有,打个比喻:
- 核心线程比作公司正式员工
- 非核心线程比作外包员工
- 阻塞队列比作需求池
- 提交任务比作提需求
3. 线程池参数有哪些?
ThreadPoolExecutor
的通用构造函数:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
-
corePoolSize
:当有新任务时,如果线程池中线程数没用达到线程池的基本大小,则会创建新的线程执行任务,否则将任务放入阻塞队列。当线程池存活的线程数总是大于corePoolSize
时,应该考虑调大corePoolSize
-
maxinumPoolSize
:当阻塞队列填满时,如果线程池中线程数没用超过最大线程数,则会创建新的线程运行任务,否则更具拒绝策略处理新任务。非核心线程类似于临时借来的资源,这些线程在空闲时间超过keepAliveTime
之后,就应该退出,避免资源浪费 -
BlockingQueue
:存储等待运行的任务 -
keepAliveTime
:非核心线程空闲后,报错存活的时间,此参数只对非核心线程有效。设置为0
,表示多余的空闲线程会呗立刻终止 -
TimeUnit
:时间单位TimeUnit.DAYS TimeUnit.HOURS TimeUnit.MINUTES TimeUnit.SECONDS TimeUnit.MILLISECONDS TimeUnit.MICROSECONDS TimeUnit.NANOSECONDS
-
ThreadFactory
:每当线程池创建一个新的线程时,都是通过线程工厂方法来完成的,在ThreadFactory
中指定要了一个方法newThread
,每当线程池需要创建新线程就会调用它public class MyThreadFactory implements ThreadFactory { private final String poolName; public MyThreadFactory(String poolName) { this.poolName = poolName; } public Thread newThread(Runnable runnable) { return new MyAppThread(runnable, poolName);//将线程池名字传递给构造函数,用于区分不同线程池的线程 } }
-
RejectedExecutionHandler
:当队列和线程池都满了时,根据拒绝策略处理新任务AbortPolicy:默认的策略,直接抛出RejectedExecutionException DiscardPolicy:不处理,直接丢弃 DiscardOldestPolicy:将等待队列队首的任务丢弃,并执行当前任务 CallerRunsPolicy:由调用线程处理该任务
-
ps:注意,这一段是根据源码分析的,具体的没贴出,这只是我的理解
- 若当前线程数小于核心线程数时,一定会创建新的线程去执行相应任务,不管线程是否有“空闲”,其实根本不会有空闲
- 承接上文,当线程池中创建新线程执行任务时(会构造一个
worker
对象,实际传进去的是runnable
任务对象,外层用worker
对象中构造的thread.start
调用其中“run
方法”,run
方法不是runnable
的run
方法,而是worker
对象的run
方法,worker
本身就实现了runnable
接口),其主要代码块会在一个while
函数内,while
函数会判断传进来的runnable
对象(worker
对象传进去的)是否为空,第一次只要传进来的runnable
不为空就直接调用runnable
的run
方法,第二次会去查找阻塞队列中是否含有任务(这是调用getTask
方法),直到从阻塞队列中找到任务后返回runnable
对象为止,说明该线程在创建后一直处于阻塞状态,直到阻塞队列中有任务之后才会进行,如果没有任务继续阻塞,这就解释了为什么线程池的不断重复利用线程的好处和效率问题 - 若当前线程数大于核心线程数时,也会执行相应方法,但前者执行的是
take()
方法,后者执行的是poll()
方法,其实线程池的核心线程和非核心线程只是我们虚构出来的数据,我们在判断出若当前线程数大于核心线程数时和keepAliveTime
的时长达到后,要除去相应线程是随机的,我们并没有给每个线程标号为核心线程 poll
方式取任务的特点是从缓存队列中取任务,最长等待keepAliveTim
e的时长,取不到返回nulltake
方式取任务的特点是从缓存队列中取任务,若队列为空,则进入阻塞状态,直到能取出对象为止
总而言之:
- 当有新任务来的时候,先看看当前的线程数有没有超过核心线程数,如果没有超过就直接新建一个线程来执行新的任务,如果超过了就看看缓冲(阻塞)队列有没有满,没满就将新任务放到缓存队列中,满了就新建一个线程来执行新的任务,如果线程池中的线程数已经达到了指定的最大线程数了,那就根据相应的策略拒绝任务
- 当缓存(阻塞)队列中的任务都执行完了的时候,线程池中的线程数如果大于核心线程数,就销毁多出来的线程(这里是每次去执行
getTask()
方法时候销毁,里面有一部分代码块会进行判断),直到线程池中的线程数等于核心线程数,此时这些线程就不会被销毁了(除非你还设置了“核心线程数也可以被销毁”,allowCoreThreadTimeOut
设置为true
,默认是false
),如果减去一个线程就是getTask
方法返回了null
,结束了前面提到的while
循环,并且线程总记录数减一,否则它们就一直处于阻塞状态,等待新的任务到来,核心线程数就保持到我们设定的量了
4. 线程池大小怎么设置?
- 如果线程池线程数量太小,当有大量请求需要处理,系统相应比较慢影响体验,甚至会出现任务队列大量堆积任务导致
OOM
- 如果线程池线程输了过大,大量线程可能会同时在争取
CPU
资源,这样会导致大量的上下文切换(cpu
给线程分配时间片,当线程的cpu
时间片用完后保存状态,以便下次继续运行),从而增加线程的执行实际,影响了整体执行效率
文章来源:https://blog.csdn.net/qq_53079156/article/details/120406398
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!