文章目录
- 1、什么是线程池,线程池有哪些(创建)
- (1) newCachedThreadPool
- (2) newFixedThreadPool
- (3) newSingleThreadExecutor
- (4) newScheduleThreadPool
- 2、为什么要使用线程池?
- 3、线程池底层工作原理
- 4、ThreadPoolExecutor 对象有哪些参数? 怎么设定核心线程数和最大线程数? 拒绝策略有哪些?
- 1) corePoolSize:核心线程数,
- (2) maximumPoolSize:最大线程数
- (3) keepAliveTime:存活时间,
- (4) unit:keepAliveTime 的单位。
- (5) workQueue:任务队列
- (6) threadFactory:线程工厂,
- (7) RejectedExecutionHandler:拒绝策略
- 线程池大小设置
- 拒绝策略
- 5、常见的线程安全的并发容器有哪些?
1、什么是线程池,线程池有哪些(创建)
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是
直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。
在JDK的java.uti.concurrent./executors类中提供了生成多种不同线程池的静态方法
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
ScheduledExecutorService newScheduledThreadPool =
Executors.newScheduledThreadPool(4);
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
然后调用他们的 execute 方法即可。
这 4 种线程池底层 全部是 ThreadPoolExecutor 对象的实现,阿里规范手册中规定线
程池采用 ThreadPoolExecutor 自定义的,实际开发也是。
(1) newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若
无可回收,则新建线程。这种类型的线程池特点是:
工作线程的创建数量几乎没有限制(其实也有限制的,数目为 Interger.
MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为
1 分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程
池重新创建一个工作线程。
在使用 CachedThreadPool 时,一定要注意控制任务的数量,否则,由于大量线程
同时运行,很有会造成系统瘫痪。
(2) newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如
果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
FixedThreadPool 是一个典型且优秀的线程池,它具有线程池提高程序效率和节省
创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任
务时,它不会释放工作线程,还会占用一定的系统资源。
(3) newSingleThreadExecutor
创建一个单线程化的 Executor,即只创建唯一的工作者线程来执行任务,它只会
用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)
执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最
大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是
活动的。
(4) newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行。例如延迟 3 秒
执行。
2、为什么要使用线程池?
(1) 线程池做的工作主要是控制运行的线程数量,处理过程中将任务放入队列,然
后在线程创建后启动这些任务,如果线程数量超过了最 大数量,超出数量的线程排队
等候,等其它线程执行完毕,再从队列中取出任务来执行。
(2) 主要特点:线程复用;控制最大并发数:管理线程。
第一: 降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消
耗。
第二: 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执
行。
第三: 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系
统资源,还会降低系统的稳定性,使用线程池可以进 行统一的分配,调优和监控
3、线程池底层工作原理
- (1) 第一步:线程池刚创建的时候,里面没有任何线程,等到有任务过来的时候才会
创建线程。当然也可以调用 prestartAllCoreThreads() 或
者 prestartCoreThread() 方法预创建 corePoolSize 个线程 - (2) 第二步:调用 execute()提交一个任务时,如果当前的工作线程数
<corePoolSize,直接创建新的线程执行这个任务 - (3) 第三步:如果当时工作线程数量>=corePoolSize,会将任务放入任务队列中缓存
- (4) 第四步:如果队列已满,并且线程池中工作线程的数量<maximumPoolSize,还是
会创建线程执行这个任务 - (5) 第五步:如果队列已满,并且线程池中的线程已达到 maximumPoolSize,这个时
候会执行拒绝策略,JAVA 线程池默认的策略是 AbortPolicy,即抛出
RejectedExecutionException 异常
4、ThreadPoolExecutor 对象有哪些参数? 怎么设定核心线程数和最大线程数? 拒绝策略有哪些?
首先我们都知道有7个参数:
1) corePoolSize:核心线程数,
在 ThreadPoolExecutor 中有一个与它相关的配置:allowCoreThreadTimeOut(默
认为 false),当 allowCoreThreadTimeOut 为 false 时,核心线程会一直存活,
哪怕是一直空闲着。而当 allowCoreThreadTimeOut 为 true 时核心线程空闲时间
超过 keepAliveTime 时会被回收。
(2) maximumPoolSize:最大线程数
线程池能容纳的最大线程数,当线程池中的线程达到最大时,此时添加任务将会采
用拒绝策略,默认的拒绝策略是抛出一个运行时错误
(RejectedExecutionException)。值得一提的是,当初始化时用的工作队列为
LinkedBlockingDeque 时,这个值将无效。
(3) keepAliveTime:存活时间,
当非核心空闲超过这个时间将被回收,同时空闲核心线程是否回收受
allowCoreThreadTimeOut 影响。
(4) unit:keepAliveTime 的单位。
(5) workQueue:任务队列
常用有三种队列,即 SynchronousQueue,LinkedBlockingDeque(无界队
列),ArrayBlockingQueue(有界队列)。
(6) threadFactory:线程工厂,
ThreadFactory 是一个接口,用来创建 worker。通过线程工厂可以对线程的一些属
性进行定制。默认直接新建线程。
(7) RejectedExecutionHandler:拒绝策略
也是一个接口,只有一个方法,当线程池中的资源已经全部使用,添加新线程被拒
绝时,会调用 RejectedExecutionHandler 的 rejectedExecution 法。默认是抛出一
个运行时异常。
线程池大小设置
线程池大小设置:
- 需要分析线程池执行的任务的特性: CPU 密集型还是 IO 密集型
- 每个任务执行的平均时长大概是多少,这个任务的执行时长可能还跟任务处理
逻辑是否涉及到网络传输以及底层系统资源依赖有关系
如果是 CPU 密集型,主要是执行计算任务,响应时间很快,cpu 一直在运行,这
种任务 cpu 的利用率很高,那么线程数的配置应该根据 CPU 核心数来决定,CPU
核心数=最大同时执行线程数,加入 CPU 核心数为 4,那么服务器最多能同时执行
4 个线程。过多的线程会导致上下文切换反而使得效率降低。那线程池的最大线程
数可以配置为 cpu 核心数+1 ;
如果是 IO 密集型,主要是进行 IO 操作,执行 IO 操
作的时间较长,这是 cpu 出于空闲状态,导致 cpu 的利用率不高,这种情况下可
以增加线程池的大小。这种情况下可以结合线程的等待时长来做判断,等待时间越
高,那么线程数也相对越多。一般可以配置 cpu 核心数的 2 倍。
一个公式:线程池设定最佳线程数目 = ((线程池设定的线程等待时间+线程
CPU 时间)/
线程 CPU 时间 )* CPU 数目
这个公式的线程 cpu 时间是预估的程序单个线程在 cpu 上运行的时间(通常使用
loadrunner 测试大量运行次数求出平均值)
拒绝策略
- AbortPolicy:直接抛出异常,默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;当然也可以根据应用场景实现
RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处
理的任务
5、常见的线程安全的并发容器有哪些?
CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap
CopyOnWriteArrayList、CopyOnWriteArraySet 采用写时复制实现线程安全
ConcurrentHashMap 采用分段锁的方式实现线程安全
还可参考以下文章:
1、Java线程池7个参数详解
2、线程池