网站首页 > 技术文章 正文
线程池的概念和工作机制
概念:首先系统空闲时在创建大量线程,这些线程的集合成为线程池。线程的生老病死都由线程池来决定。
工作机制:当有任务到来时,提交给线程池,由线程池来指定线程执行任务。线程池会在内部寻找是否有可以执行任务的线程。任务执行完成后,线程不会被销毁,而是进入空闲状态。一个线程一个时刻只能执行一个任务,但是却可以向线程池提交多个任务(只不过后来的任务可能需要等待)。
为什么要使用线程池(好处)?
降低资源的消耗:线程在创建和销毁时都是很耗费资源和时间的,我们希望通过一种机制,可以避免频繁地创建和销毁线程。
提高响应速度:因为线程池中的线程大部分是事先系统在空闲时创建的,所以当有任务到来的时候,可以直接使用已有线程,而不用去创建。任务一来就可以直接执行。
控制线程的并发数量:当线程的数目非常多时,我们就需要考虑高并发带来的一系列问题。多个线程可能因为争夺资源而使系统崩溃,运用多线程可以有效的控制线程的数目。
提高线程的可管理性:可以对某些线程设定延时执行(DelayQueue)、或者循环执行等策略。
线程池ThreadPoolExecutor
1.// 先看一条创建线程池的语句
2.ExecutorService service = Executors.newFixedThreadPool(5);
在这里需要提到几个接口和类:Executor, ExecutorService, Executors, ThreadPoolExecutor
关于这几个接口和类,这里有篇文章讲得更详细:传送门
Executor:理解为执行器,内部只要一个执行方法 execute(Runnable command)。是线程池的一个核心接口。
ExecutorService:继承了Executor,并做了拓展,增加了一堆供程序员开发用的api,所以你用起ExectorService才会那么舒服,其次,它还增加了对线程池生命周期的概念,一个线程池的声明周期有三种状态:运行、关闭和终止。
Executors:是一个用来创建线程池的工具类(像Collections类的存在),其返回的线程池都是实现了ExecutorService接口。
ThreadPoolExecutor:该类继承AbstractExecutorService抽象类,实现了ExecutorService接口,内部维护着一个线程池。一般我们只需要通过这个类的构造函数来配置线程池就好了。
ThreadPoolExecutor这是学习多线程的开头,通过学习该类的参数,来慢慢理解线程池内部的结构。
通过ThreadPoolExecutor的构造函数看参数
参数虽然多,但是确实必须理解的,而且其中有两种是不用去理会的
corePoolSize:核心线程的最大数目。
核心线程:当线程池在新建线程时,如果当前池内的线程数 < corePoolSize,那么创建出来的就是核心线程。
核心线程默认情况下会一直存在于线程池中,即使这个线程一直不做事。不过我们可以通过ThreadPoolExecutor的allowCoreThreadTimeOut属性,设置其为true,那么线程池就会去回收长时间不做事的线程了
maximumPoolSize
线程池中的最大线程数目 = 核心线程数 + 非核心线程数。
keepAliveTime
该线程池中,非核心线程的闲置时间,超时销毁。
TimeUtil util
keepAliveTime的单位,TimeUtil是一个枚举类型,包括了
NANOSECONDS:微毫秒
MICROSECONDS:微秒
MILLISECONDS:毫秒
SECONDS:秒
MINUTES:分钟
HOURS:小时
DAYS:天
BlockingQueue
任务队列,就是我们提交的任务,里面都是等待被执行的Runnable对象。
当核心线程都在忙的时候,新提交的任务就会被放在队列里等待被执行。如果队列满了,那么就开始创建非核心线程执行任务。
常见的队列类型
SynchronousQueue:这个队列拿到新的任务之后,会直接提交给线程处理,不会保留任务。如果所有的线程都在工作,那么线程池就创建一个新的线程。但是我们知道maximumPoolSize就是用来限制线程的数目的,如果超过这个值,就会报错,所以使用这种队列的时候,需要把maximumPoolSize设置为Integer.MAX_VALUE,即无限大。
LinkedBlockingQueue:这个队列接受到任务时,如果当前线程数小于核心数目,则会创建新的线程。如果线程数目已经达到核心线程的数目,那么新来的任务就会放入队列中。这意味着什么?意味着线程的总数目永远都是 <= 核心线程数目,那么,maximumPoolSize这个属性就相当于废掉了。
ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果线程的数目,没有达到corePoolSize,就新建核心线程执行任务。如果线程数目达到了corePoolSize时,还有新任务,新来的任务就进入队列,当队列满了,再创建非核心线程帮忙执行任务。如果队列满了,线程数目又达到了maximumPoolSize,怎么办呢?这就是涉及到后面的拒绝策略了。
DelayQueue:队列内的元素必须实现Delayed接口,这意味着你传进去的任务必须先实现Delayed接口。这个队列接收到新任务时,首先进入队列,然后只有达到制定的延时时间,才会执行任务。(这里有篇讲DelayQueue的文章:传送门)
ThreadFactory
创建线程的方式,这是一个接口,你new他的时候需要实现他的Thread newThread(Runnable r)方法,一般用不上。
RejectedExecutionHandler
简单讲,用来抛异常的。比如当遇到上面两种错误:ArrayBlockingQueue队满,线程数目也到顶时,就要报错;SynchronousQueue那里,线程数目达到maximumPoolSize而引发的错误。就由handler抛异常。
如何添加任务进入线程池?
常用线程池
一般来讲,Executors提供的线程池已经够用了,如果实在没有符合自己要求的,那么可以自己配置。
Java通过Executors提供四种线程池,这几个线程池都是直接或者间接通过配置ThreadPoolExecutor的参数实现的。
FixedThreadPool
定长线程池,创建时声明最大的线程数目。超出的线程会在队列中等待。
创建方法,两种。一般第一种用法就够了
// nThreads:最大线程数目,即 maximumPoolSize
ExecutorService service = Executors.newFixedThreadPool(nthread);
// threadFactory 创建线程的方法
ExecutorService service = Executors.newFixedThreadPool(nthread, threadFactory);
// ExecutorService 源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
注意到,它用的队列是LinkedBlockingQueue,应该想到任务提交后,线程池会做出哪些反应。
CachedThreadPool
· 可缓存线程池,线程数目无限制,有空闲线程则复用,没有就创建新的线程。(数目不限,有闲则用,无闲新建)。但是如果空闲太久,线程池又会自动地销毁空闲线程。
· 一定程度减少了频繁创建/销毁线程的花销。
· 创建方法
ExecutorService service = Executors.newCachedThreadPool();
// ThreadPoolExecutor 源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
maximumPoolSize = Integer.MAX_VALUE
这里用的是SynchronousQueue队列,这个队列最终是不存储任何元素的。对于每个put/offer操作,必须等待一个take/poll操作。
ScheduleThreadPool
支持定时任务及周期性任务执行
创建方法
ExecutorService service = Executors.newScheduleThreadPool(corePoolSize);
// ThreadPoolExecutor源码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
使用的是DelayQueue队列maximumPoolSize = Integer.MAX_VALUEDEFAULT_KEEPALIVE_MILLIS 默认是 10L,这里是10s
SingleThreadExecutor
· 单线程的线程池,有且只有一个线程在执行任务。所有任务按照入队的顺序来执行,先来先服务
· 创建方法
LinkedBlockingQueue 队列
如何关闭线程池
线程池有创建,那么也需要有关闭。ExecutorService提供了两个方法来关闭线程池
· shutdown():执行后停止接受新任务,会把队列中的任务执行完毕。
· shutdownNow():也是停止接受新任务,但会终端所有的任务,将线程池的状态变为stop。
pool.awaitTermination(1, TimeUnit.SECONDS) 会每隔一秒钟检查一次是否执行完毕(状态为 TERMINATED),当从 while 循环退出时就表明线程池已经完全终止了。
线程池的拒绝策略
当线程池中的任务缓存队列已满,并且线程池中的线程数目达到最大线程数量,如果还有任务要到来,就要采用拒绝策略,通常有以下四种:
· 丢弃任务并且抛出异常
· 丢弃任务不抛出异常
· 丢弃队列最前面的任务,并尝试重新开始执行任务
· 由调用线程处理该任务
程池的概念和工作机制
概念:首先系统空闲时在创建大量线程,这些线程的集合成为线程池。线程的生老病死都由线程池来决定。
工作机制:当有任务到来时,提交给线程池,由线程池来指定线程执行任务。线程池会在内部寻找是否有可以执行任务的线程。任务执行完成后,线程不会被销毁,而是进入空闲状态。一个线程一个时刻只能执行一个任务,但是却可以向线程池提交多个任务(只不过后来的任务可能需要等待)。
为什么要使用线程池(好处)?
降低资源的消耗:线程在创建和销毁时都是很耗费资源和时间的,我们希望通过一种机制,可以避免频繁地创建和销毁线程。
提高响应速度:因为线程池中的线程大部分是事先系统在空闲时创建的,所以当有任务到来的时候,可以直接使用已有线程,而不用去创建。任务一来就可以直接执行。
控制线程的并发数量:当线程的数目非常多时,我们就需要考虑高并发带来的一系列问题。多个线程可能因为争夺资源而使系统崩溃,运用多线程可以有效的控制线程的数目。
提高线程的可管理性:可以对某些线程设定延时执行(DelayQueue)、或者循环执行等策略。
线程池ThreadPoolExecutor
1.// 先看一条创建线程池的语句
2.ExecutorService service = Executors.newFixedThreadPool(5);
在这里需要提到几个接口和类:Executor, ExecutorService, Executors, ThreadPoolExecutor
关于这几个接口和类,这里有篇文章讲得更详细:传送门
Executor:理解为执行器,内部只要一个执行方法 execute(Runnable command)。是线程池的一个核心接口。
ExecutorService:继承了Executor,并做了拓展,增加了一堆供程序员开发用的api,所以你用起ExectorService才会那么舒服,其次,它还增加了对线程池生命周期的概念,一个线程池的声明周期有三种状态:运行、关闭和终止。
Executors:是一个用来创建线程池的工具类(像Collections类的存在),其返回的线程池都是实现了ExecutorService接口。
ThreadPoolExecutor:该类继承AbstractExecutorService抽象类,实现了ExecutorService接口,内部维护着一个线程池。一般我们只需要通过这个类的构造函数来配置线程池就好了。
ThreadPoolExecutor这是学习多线程的开头,通过学习该类的参数,来慢慢理解线程池内部的结构。
通过ThreadPoolExecutor的构造函数看参数
参数虽然多,但是确实必须理解的,而且其中有两种是不用去理会的
corePoolSize:核心线程的最大数目。
核心线程:当线程池在新建线程时,如果当前池内的线程数 < corePoolSize,那么创建出来的就是核心线程。
核心线程默认情况下会一直存在于线程池中,即使这个线程一直不做事。不过我们可以通过ThreadPoolExecutor的allowCoreThreadTimeOut属性,设置其为true,那么线程池就会去回收长时间不做事的线程了
maximumPoolSize
线程池中的最大线程数目 = 核心线程数 + 非核心线程数。
keepAliveTime
该线程池中,非核心线程的闲置时间,超时销毁。
TimeUtil util
keepAliveTime的单位,TimeUtil是一个枚举类型,包括了
NANOSECONDS:微毫秒
MICROSECONDS:微秒
MILLISECONDS:毫秒
SECONDS:秒
MINUTES:分钟
HOURS:小时
DAYS:天
BlockingQueue
任务队列,就是我们提交的任务,里面都是等待被执行的Runnable对象。
当核心线程都在忙的时候,新提交的任务就会被放在队列里等待被执行。如果队列满了,那么就开始创建非核心线程执行任务。
常见的队列类型
SynchronousQueue:这个队列拿到新的任务之后,会直接提交给线程处理,不会保留任务。如果所有的线程都在工作,那么线程池就创建一个新的线程。但是我们知道maximumPoolSize就是用来限制线程的数目的,如果超过这个值,就会报错,所以使用这种队列的时候,需要把maximumPoolSize设置为Integer.MAX_VALUE,即无限大。
LinkedBlockingQueue:这个队列接受到任务时,如果当前线程数小于核心数目,则会创建新的线程。如果线程数目已经达到核心线程的数目,那么新来的任务就会放入队列中。这意味着什么?意味着线程的总数目永远都是 <= 核心线程数目,那么,maximumPoolSize这个属性就相当于废掉了。
ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果线程的数目,没有达到corePoolSize,就新建核心线程执行任务。如果线程数目达到了corePoolSize时,还有新任务,新来的任务就进入队列,当队列满了,再创建非核心线程帮忙执行任务。如果队列满了,线程数目又达到了maximumPoolSize,怎么办呢?这就是涉及到后面的拒绝策略了。
DelayQueue:队列内的元素必须实现Delayed接口,这意味着你传进去的任务必须先实现Delayed接口。这个队列接收到新任务时,首先进入队列,然后只有达到制定的延时时间,才会执行任务。(这里有篇讲DelayQueue的文章:传送门)
ThreadFactory
创建线程的方式,这是一个接口,你new他的时候需要实现他的Thread newThread(Runnable r)方法,一般用不上。
RejectedExecutionHandler
简单讲,用来抛异常的。比如当遇到上面两种错误:ArrayBlockingQueue队满,线程数目也到顶时,就要报错;SynchronousQueue那里,线程数目达到maximumPoolSize而引发的错误。就由handler抛异常。
如何添加任务进入线程池?
常用线程池
一般来讲,Executors提供的线程池已经够用了,如果实在没有符合自己要求的,那么可以自己配置。
Java通过Executors提供四种线程池,这几个线程池都是直接或者间接通过配置ThreadPoolExecutor的参数实现的。
FixedThreadPool
定长线程池,创建时声明最大的线程数目。超出的线程会在队列中等待。
创建方法,两种。一般第一种用法就够了
// nThreads:最大线程数目,即 maximumPoolSize
ExecutorService service = Executors.newFixedThreadPool(nthread);
// threadFactory 创建线程的方法
ExecutorService service = Executors.newFixedThreadPool(nthread, threadFactory);
// ExecutorService 源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
注意到,它用的队列是LinkedBlockingQueue,应该想到任务提交后,线程池会做出哪些反应。
CachedThreadPool
· 可缓存线程池,线程数目无限制,有空闲线程则复用,没有就创建新的线程。(数目不限,有闲则用,无闲新建)。但是如果空闲太久,线程池又会自动地销毁空闲线程。
· 一定程度减少了频繁创建/销毁线程的花销。
· 创建方法
ExecutorService service = Executors.newCachedThreadPool();
// ThreadPoolExecutor 源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
maximumPoolSize = Integer.MAX_VALUE
这里用的是SynchronousQueue队列,这个队列最终是不存储任何元素的。对于每个put/offer操作,必须等待一个take/poll操作。
ScheduleThreadPool
支持定时任务及周期性任务执行
创建方法
ExecutorService service = Executors.newScheduleThreadPool(corePoolSize);
// ThreadPoolExecutor源码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
使用的是DelayQueue队列maximumPoolSize = Integer.MAX_VALUEDEFAULT_KEEPALIVE_MILLIS 默认是 10L,这里是10s
SingleThreadExecutor
· 单线程的线程池,有且只有一个线程在执行任务。所有任务按照入队的顺序来执行,先来先服务
· 创建方法
LinkedBlockingQueue 队列
如何关闭线程池
线程池有创建,那么也需要有关闭。ExecutorService提供了两个方法来关闭线程池
· shutdown():执行后停止接受新任务,会把队列中的任务执行完毕。
· shutdownNow():也是停止接受新任务,但会终端所有的任务,将线程池的状态变为stop。
pool.awaitTermination(1, TimeUnit.SECONDS) 会每隔一秒钟检查一次是否执行完毕(状态为 TERMINATED),当从 while 循环退出时就表明线程池已经完全终止了。
线程池的拒绝策略
当线程池中的任务缓存队列已满,并且线程池中的线程数目达到最大线程数量,如果还有任务要到来,就要采用拒绝策略,通常有以下四种:
· 丢弃任务并且抛出异常
· 丢弃任务不抛出异常
· 丢弃队列最前面的任务,并尝试重新开始执行任务
· 由调用线程处理该任务
猜你喜欢
- 2024-10-22 线程池调优之动态参数配置 动态设置线程池大小
- 2024-10-22 java线程池参数及使用 java线程池的用法
- 2024-10-22 面试官:说说你对线程池的了解 线程池实现原理面试
- 2024-10-22 「每日分享」高阶程序员需要掌握的常见性能优化策略
- 2024-10-22 池化技术学习 池化方法
- 2024-10-22 「Java基础」「多线程」-线程池 java多个线程池
- 2024-10-22 线程池配置的常见误区 线程池配置参数有哪些
- 2024-10-22 创建线程池参数有哪些作用? 创建线程池的7个参数
- 2024-10-22 一文搞懂!多线程之间的通信及线程池
- 2024-10-22 码农大叔带你——解析线程池 线程池的主要处理流程
你 发表评论:
欢迎- 最近发表
-
- 吴谨言专访大反转!痛批耍大牌后竟翻红,六公主七连发力显真诚
- 港股2月28日物业股涨幅榜:CHINAOVSPPT涨1.72%位居首位
- 港股2月28日物业股午盘:CHINAOVSPPT涨1.72%位居首位
- 港股3月2日物业股涨幅榜:CHINAOVSPPT涨1.03%位居首位
- 港股3月2日物业股午盘:CHINAOVSPPT涨1.03%
- 天赋与心痛的背后:邓鸣贺成长悲剧引发的深刻反思
- 冯小刚女儿徐朵追星范丞丞 同框合照曝光惹人羡,回应网友尽显亲民
- “资本大佬”王冉:51岁娶小17岁童瑶,并承诺余生为娇妻保驾护航
- 港股3月2日物业股午盘:CHINAOVSPPT涨1.03%位居首位
- 「IT之家开箱」vivo S15 图赏:双镜云窗,盛夏风光
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)