网站首页 > java教程 正文
什么是线程池
线程池的概念大家应该都很清楚,帮我们重复管理线程,避免创建大量的线程增加开销。除了降低开销以外,线程池也可以提高响应速度
创建线程池需要使用 ThreadPoolExecutor 类,它的构造函数参数如下:
public ThreadPoolExecutor(int corePoolSize, //核心线程的数量 int maximumPoolSize, //最大线程数量 long keepAliveTime, //超出核心线程数量以外的线程空余存活时间 TimeUnit unit, //存活时间的单位 BlockingQueue<Runnable> workQueue, //保存待执行任务的队列 ThreadFactory threadFactory, //创建新线程使用的工厂 RejectedExecutionHandler handler // 当任务无法执行时的处理器 ) {...}
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); //1.当前池中线程比核心数少,新建一个线程执行任务 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } //2.核心池已满,但任务队列未满,添加到队列中 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) //如果这时被关闭了,拒绝任务 reject(command); else if (workerCountOf(recheck) == 0) //如果之前的线程已被销毁完,新建一个线程 addWorker(null, false); } //3.核心池已满,队列已满,试着创建一个新线程 else if (!addWorker(command, false)) reject(command); //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务 }
可以看到,线程池处理一个任务主要分三步处理,代码注释里已经介绍了,我再用通俗易懂的例子解释一下:
(线程比作员工,线程池比作一个团队,核心池比作团队中核心团队员工数,核心池外的比作外包员工)
- 有了新需求,先看核心员工数量超没超出最大核心员工数,还有名额的话就新招一个核心员工来做 需要获取全局锁
- 核心员工已经最多了,HR 不给批 HC 了,那这个需求只好攒着,放到待完成任务列表吧
- 如果列表已经堆满了,核心员工基本没机会搞完这么多任务了,那就找个外包吧 需要获取全局锁
- 如果核心员工 + 外包员工的数量已经是团队最多能承受人数了,没办法,这个需求接不了了
由于 1 和 3 新建线程时需要获取全局锁,这将严重影响性能。因此 ThreadPoolExecutor 这样的处理流程是为了在执行 execute() 方法时尽量少地执行 1 和 3,多执行 2。
在 ThreadPoolExecutor 完成预热后(当前线程数不少于核心线程数),几乎所有的 execute() 都是在执行步骤 2。
ThreadPoolExecutor 构造函数的参数
- corePoolSize:核心线程池数量
在线程数少于核心数量时,有新任务进来就新建一个线程,即使有的线程没事干
等超出核心数量后,就不会新建线程了,空闲的线程就得去任务队列里取任务执行了
- maximumPoolSize:最大线程数量
包括核心线程池数量 + 核心以外的数量
如果任务队列满了,并且池中线程数小于最大线程数,会再创建新的线程执行任务
- keepAliveTime:核心池以外的线程存活时间,即没有任务的外包的存活时间
如果给线程池设置 allowCoreThreadTimeOut(true),则核心线程在空闲时头上也会响起死亡的倒计时
如果任务是多而容易执行的,可以调大这个参数,那样线程就可以在存活的时间里有更大可能接受新任务
- workQueue:保存待执行任务的阻塞队列
不同的任务类型有不同的选择
- threadFactory:每个线程创建的地方
可以给线程起个好听的名字,设置个优先级啥的
- handler:饱和策略,大家都很忙,咋办呢,有四种策略
CallerRunsPolicy:只要线程池没关闭,就直接用调用者所在线程来运行任务
AbortPolicy:直接抛出 RejectedExecutionException 异常
DiscardPolicy:悄悄把任务放生,不做了
DiscardOldestPolicy:把队列里待最久的那个任务扔了,然后再调用 execute() 试试看能行不
我们也可以实现自己的 RejectedExecutionHandler 接口自定义策略,比如如记录日志什么的
保存待执行任务的阻塞队列
当线程池中的核心线程数已满时,任务就要保存到队列中了。线程池中使用的队列是 BlockingQueue 接口,常用的实现有如下几种:
- ArrayBlockingQueue:基于数组、有界,按 FIFO(先进先出)原则对元素进行排序
- LinkedBlockingQueue:基于链表,按FIFO (先进先出) 排序元素 吞吐量通常要高于 ArrayBlockingQueue Executors.newFixedThreadPool() 使用了这个队列
- SynchronousQueue:不存储元素的阻塞队列 每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态 吞吐量通常要高于 LinkedBlockingQueue Executors.newCachedThreadPool使用了这个队列
- PriorityBlockingQueue:具有优先级的、无限阻塞队列
◆ ◆ ◆ ◆ ◆
关注并后台回复 “面试”,即可免费获取
最新2019BAT大厂面试题,更有大数据
视频等你拿
·END·
后端开发技术
追求技术的深度
vx:后端开发技术
创建线程池
1、先定义线程池的几个关键属性的值:
设置核心池的数量为 CPU 数的两倍,一般是 4、8,好点的 16 个线程
最大线程数设置为 64
空闲线程的存活时间设置为 1 秒
如何设置核心线程数
- IO密集型 CPU处理器个数*2
IO密集型,是指系统大部分时间在跟I/O交互,而这个时间线程不会占用CPU来处理
- CPU密集型 CPU处理器个数+1
CPU密集型,是指系统大部分时间是在做程序正常的计算任务,例如数字运算、赋值、分配内存、内存拷贝、循环、查找、排序等,这些处理都需要CPU来完成。
2、根据处理的任务类型选择不同的阻塞队列
如果是要求高吞吐量的,可以使用 SynchronousQueue 队列;如果对执行顺序有要求,可以使用 PriorityBlockingQueue;如果最大积攒的待做任务有上限,可以使用 LinkedBlockingQueue。
3、创建自己的 ThreadFactory
在其中为每个线程设置个名称
4、创建线程池
public class ThreadPoolManager { private final String TAG = this.getClass().getSimpleName(); private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2; // 核心线程数为 CPU数*2 private static final int MAXIMUM_POOL_SIZE = 64; // 线程队列最大线程数 private static final int KEEP_ALIVE_TIME = 1; // 保持存活时间 1秒 private final BlockingQueue<Runnable> mWorkQueue = new LinkedBlockingQueue<>(128); private final ThreadFactory DEFAULT_THREAD_FACTORY = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { Thread thread = new Thread(r, TAG + " #" + mCount.getAndIncrement()); thread.setPriority(Thread.NORM_PRIORITY); return thread; } }; private ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, mWorkQueue, DEFAULT_THREAD_FACTORY, new ThreadPoolExecutor.DiscardOldestPolicy()); private static volatile ThreadPoolManager mInstance = new ThreadPoolManager(); public static ThreadPoolManager getInstance() { return mInstance; } public void addTask(Runnable runnable) { mExecutor.execute(runnable); } @Deprecated public void shutdownNow() { mExecutor.shutdownNow(); } }
- 上一篇: RocketMQ中的线程池是如何创建的?
- 下一篇: 面试官:并发编程系列之如何正确使用线程池?
猜你喜欢
- 2024-11-23 不清楚Java线程池实现原理?那你应该收藏这篇文章!「源码分析」
- 2024-11-23 Java基础——Java多线程(线程的创建方式)
- 2024-11-23 「一文搞懂」Java线程池实现原理
- 2024-11-23 Java线程池
- 2024-11-23 java线程池实现原理以及应用场景说明
- 2024-11-23 快速弄懂Java线程池
- 2024-11-23 java线程池原理浅析
- 2024-11-23 彻底了解线程池的原理——40行从零开始自己写线程池
- 2024-11-23 Java并发编程(8):Executor框架 - 可扩展线程池WorkStealingPool
- 2024-11-23 java中的线程池
你 发表评论:
欢迎- 最近发表
-
- 五,网络安全IDA Pro反汇编工具初识及逆向工程解密实战
- 「JAVA8」- Lambda 表达式(java lambda表达式原理)
- 深入探讨Java代码保护:虚拟机保护技术的新时代
- Nginx反向代理原理详解(图文全面总结)
- 逆向拆解日本IT,哪些Java技术栈薪资溢价高
- mybatis 逆向工程使用姿势不对,把表清空了,心里慌的一比
- Spring Boot集成ProGuard轻松实现Java 代码混淆, Java 应用固若金汤
- 从 Java 代码逆向工程生成 UML 类图和序列图
- 人与人相处:尊重是标配,靠谱是高配,厚道是顶配
- Windows系统安装日期如何修改(windows10怎么修改安装日期)
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)