文章目录
- JUC并发编程——线程池底层实现
-
- 1、线程池介绍
- 2、线程池的简单使用
- 3、线程池底层实现
-
- 3.1、三大方法源码分析
- 3.2、七大参数
- 3.3、四大拒绝策略
- 4、自定义创建线程池
JUC并发编程——线程池底层实现 1、线程池介绍
池化技术池化技术是一种很常见的编程技巧,在请求量大时能明显优化应用性能,降低系统频繁建连的资源开销。我们日常工作中常见的有数据库连接池、线程池、对象池等,它们的特点都是将 “昂贵的”、“费时的” 的资源维护在一个特定的 “池子” 中,规定其最小连接数、最大连接数、阻塞队列等配置,方便进行统一管理和复用,通常还会附带一些探活机制、强制回收、监控一类的配套功能。
线程池在真实的生产环境中,可能需要很多线程来支撑整个应用,当线程数量非常多时 ,反而会耗尽CPU资源。如果不对线程进行控制与管理,反而会影响程序的性能。
线程开销主要包括: 创建与启动线程的开销; 线程销毁开销; 线程调度的开销; 线程数量受限CPU处理器数量。
线程池就是有效使用线程的一种常用方式。
线程池内部可以预先创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交给线程池,线程池将这些任务缓存在工作队列中,线程池中的工作线程不断地从队列中取出任务并执行。
线程池的好处:
- 减低资源消耗
- 提高响应速度
- 方便管理
文章图片
2、线程池的简单使用 JDK提供了一套Executor工具类,可以帮助开发人员有效的使用线程池。
线程池的创建和使用
public class ThreadPool {
public static void main(String[] args) {ExecutorService threadExecutor = Executors.newFixedThreadPool(3);
//创建3个线程大小的线程池
try {
//向线程池中提交5个任务,这5个任务存储到线程池的阻塞队列中, 线程池中的3个线程就从阻塞队列中取任务执行
for (int i = 0;
i < 5;
i++) {
//使用线程池后,用线程池创建线程
threadExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//使用完后关闭线程池
threadExecutor.shutdown();
}
}
}
3、线程池底层实现 3.1、三大方法源码分析
查看Executors工具类中newCachedThreadPool(), newSingleThreadExcecutor(), newFixedThreadPool()源码:
newCachedThreadPool():创建一个大小可伸缩的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
newSingleThreadExcecutor():创建单个线程大小的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
newFixedThreadPool():创建一个固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
3.2、七大参数
Excutors工具类中返回线程池的方法底层都使用了ThreadPoolExecutor线程池,这些方法都是ThreadPoolExecutor线程池的封装:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
各个参数含义:
corePoolSize, 指定线程池中核心线程的数量。
maxinumPoolSize,指定线程池中最大线程数量。
keepAliveTime,当线程池线程的数量超过corePoolSize时,多余的空闲线程的存活时长,即空闲线程在多长时长内销毁。
unit, 是keepAliveTime时长单位。
workQueue,任务队列,把任务提交到该任务队列中等待执行。
threadFactory,线程工厂,用于创建线程。
handler拒绝策略,当任务太多来不及处理时,如何拒绝。
3.3、四大拒绝策略
ThreadPoolExecutor构造方法的最后一个参数指定了拒绝策略.当提交给线程池的任务量超过实际承载能力时,如何处理? 即线程池中的线程已经用完了,等待队列也满了,无法为新提交的任务服务,可以通过拒绝策略来处理这个问题. JDK提供了四种拒绝策略:
● AbortPolicy 策略,会抛出异常(默认使用)。
文章图片
● CallerRunsPolicy 策略,只要线程池没关闭,会在调用者线程中运行当前被丢弃的任务。
● DiscardOldestPolicy 将任务队列中最老的任务丢弃,尝试再次提交新任务。
● DiscardPolicy 直接丢弃这个无法处理的任务。
参考文章:http://www.bjpowernode.com/javathread/1233.html
4、自定义创建线程池 在阿里巴巴Java开发手册中说明,创建线程池最好用ThreadPoolExecutor,可以避免OOM(内存溢出):
文章图片
创建线程池代码说明:
ExecutorService threadExecutor = new ThreadPoolExecutor(
2, //指定线程池中核心线程的数量
5, //指定线程池中最大线程数量
3, //空闲线程在多长时长内销毁
TimeUnit.SECONDS, //单位 s
new LinkedBlockingQueue<>(10), //基于链表实现的可选容量的阻塞队列,把任务提交到该任务队列中等待执行
Executors.defaultThreadFactory(),//默认的线程池工厂
new ThreadPoolExecutor.CallerRunsPolicy());
//拒绝策略:将任务队列中最老的任务丢弃,尝试再次提交新任务
补充:如何设置线程池中最大线程数量1、CPU密集型:最大线程数量参考值应等于CPU核数 + 1
//获取CPU核数
Runtime.getRuntime().availableProcessors()
cpu使用率较高(也就是一些复杂运算,逻辑处理)
2、I/O密集型:最大线程数量参考值应等于CPU核数 x 2
【JUC并发编程|JUC并发编程——线程池介绍和底层实现】CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)
推荐阅读
- 游戏|一个niubility的Vue游戏,真厉害!
- 游戏|一个niubility的Vue游戏,真厉害!(收藏)
- 再也不敢精通Java了——get/set篇
- 大厂实战面经|华为java软件开发 一面+二面+三面
- Spring|SpringBoot学习笔记二(进阶内容)
- #|【JUC系列】并发容器之ConcurrentLinkedQueue(JDK1.8版)
- 数据结构与算法|马踏棋盘算法
- 算法与数据结构|电信保温杯笔记——代码随想录 刷题攻略 回溯算法
- Java之集合