新手一看就懂的线程池
那相信大家也能感受到,其实用多线程是很麻烦的,包括线程的创建、销毁和调度等等,而且我们平时工作时好像也并没有这样来 new 一个线程,其实是因为很多框架的底层都用到了线程池。
线程池是帮助我们管理线程的工具,它维护了多个线程,可以降低资源的消耗,提高系统的性能。
并且通过使用线程池,我们开发人员可以更好的把精力放在任务代码上,而不去管线程是如何执行的,实现任务提交和执行的解藕。
本文将从是何、为何、如何的角度来讲解线程池:
线程池是什么
为什么要用线程池
怎么用线程池
线程池 Thread Pool
线程池是一种池化的技术,类似的还有数据库连接池、HTTP 连接池等等。
池化的思想主要是为了减少每次获取和结束资源的消耗,提高对资源的利用率。
比如在一些偏远地区打水不方便的,大家会每段时间把水打过来存在池子里,这样平时用的时候就直接来取就好了。
线程池同理,正是因为每次创建、销毁线程需要占用太多系统资源,所以我们建这么一个池子来统一管理线程。用的时候从池子里拿,不用了就放回来,也不用你销毁,是不是方便了很多?
Java 中的线程池是由 juc
即 java.util.concurrent
包来实现的,最主要的就是 ThreadPoolExecutor
这个类。具体怎么用我们下文再说。
线程池的好处
在多线程的第一篇文章中我们说过,进程会申请资源,拿来给线程用,所以线程是很占用系统资源的,那么我们用线程池来统一管理线程就能够很好的解决这种资源管理问题。
比如因为不需要创建、销毁线程,每次需要用的时候我就去拿,用完了之后再放回去,所以节省了很多资源开销,可以提高系统的运行速度。
而统一的管理和调度,可以合理分配内部资源,根据系统的当前情况调整线程的数量。
那总结来说有以下 3 个好处:
降低资源消耗:通过重复利用现有的线程来执行任务,避免多次创建和销毁线程。
提高相应速度:因为省去了创建线程这个步骤,所以在拿到任务时,可以立刻开始执行。
提供附加功能:线程池的可拓展性使得我们可以自己加入新的功能,比如说定时、延时来执行某些线程。
说了这么多,终于到了今天的重点,我们来看下究竟怎么用线程池吧~
线程池的实现
Java 给我们提供了 Executor
接口来使用线程池。
我们常用的线程池有这两大类:
ThreadPoolExecutor
ScheduledThreadPoolExecutor
它俩的区别呢,就是第一个是普通的,第二个是可以定时执行的。
当然还有其他线程池,比如 JDK 1.7 才出现的 ForkJoinPool
,可以把大任务分割成小任务来执行,最后再大一统。
那么任务提交到一个线程池之后,它会经历一个怎样的过程呢?
执行过程
线程池在内部实际上采用了生产者消费者模型(还不清楚这个模型的在文章开头有改文章的链接)将线程和任务解藕,从而使线程池同时管理任务和线程。
当任务提交到线程池里之后,需要经过以下流程:
首先它检查核心线程池是否已满。这个核心线程池,就是不管用户量多少线程池始终维护的线程的池子。比如说线程池的总容量最多能装 100 个线程,核心线程池我们设置为 50,那么就无论用户量有多少,都保持 50 个线程活着。这个数字当然是根据具体的业务需求来决定的。
阻塞队列,就是
BlockingQueue
,在生产者消费者这节里提到过。最后判断线程池是否已满,就是判断是不是已经有 100 个线程了,而不是 50 个。
如果已经满了,所以不能继续创建线程了,就需要按照饱和策略或者叫做拒绝策略来处理。这个饱和策略我们下文再讲。
ThreadPoolExecutor
我们主要说下 ThreadPoolExecutor
,它是最常用的线程池。
这里我们可以看到,这个类里有 4 个构造方法,点进去仔细看,其实前三个都 call 了最后一个,所以我们只需要看最后一个就好。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {...
}
这里我们来仔细看下这几个参数:
corePoolSize:这个就是上文提到过的核心线程池的大小,在核心里的线程是永远不会失业的。
corePoolSize the number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set
maximumPoolSize:线程池的最大容量。
maximumPoolSize the maximum number of threads to allow in the pool
keepAliveTime:存活时间。这个时间指的是,当线程池中的线程数量大于核心线程数,这些线程闲着之后,多久销毁它们。
keepAliveTime when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
unit:对应上面存活时间的时间单位。
unit the time unit for the {@code keepAliveTime} argument
workQueue:这是一个阻塞队列,其实线程池也是生产者消费者模型的一种,任务 - 相当于生产者,线程 - 相当于消费者,所以这个阻塞队列是用来协调生产和消费的进度的。
workQueue the queue to use for holding tasks before they are executed.
threadFactory:这里用到了工程模式,用来创建线程的。
threadFactory the factory to use when the executor creates a new thread
handler:这个就是拒绝策略。
handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached
所以我们可以通过自己传入这 7 个参数构造线程池,当然了,贴心的 Java 也给我们包装好了几类线程池可以很方便的拿来使用。
newCachedThreadPool
newFixedThreadPool
newSingleThreadExecutor
我们具体来看每个的含义和用法。
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
这里我们可以看到,
核心线程池数量为 0,也就是它不会永久保留任何线程;
最大容量是
Integer.MAX_VALUE
;每个线程的存活时间是 60 秒,也就是如果 1 分钟没有用这个线程就被回收了;
最后用到了同步队列。
它的适用场景在源码里有说:
These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks.
来看怎么用:
public class newCacheThreadPool {public static void main(String[] args) {// 创建一个线程池ExecutorService executorService = Executors.newCachedThreadPool();// 向线程池提交任务for (int i = 0; i < 50; i++) {executorService.execute(new Task());//线程池执行任务}executorService.shutdown();}
}
执行结果:
可以很清楚的看到,线程 1、2、3、5、6 都很快重用了。
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
这个线程池的特点是:
线程池中的线程数量是固定的,也是我们创建线程池时需要穿入的参数;
超出这个数量的线程就需要在队列中等待。
它的适用场景是:
Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
public class FixedThreadPool {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < 200; i++) {executorService.execute(new Task());}executorService.shutdown();}
}
这里我限制了线程池里最多有 10 个线程,哪怕有 200 个任务需要执行,也只有 1-10 这 10 个线程可以运行。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
这个线程池顾名思义,里面只有 1 个线程。
适用场景是:
Creates an Executor that uses a single worker thread operating off an unbounded queue.
我们来看下效果。
public class SingleThreadPool {public static void main(String[] args) {ExecutorService executorService = Executors.newSingleThreadExecutor();for (int i = 0; i < 100; i++) {executorService.execute(new Task());}executorService.shutdown();}
}
这里在出结果的时候我能够明显的感觉到有些卡顿,这在前两个例子里是没有的,毕竟这里只有一个线程在运行嘛。
小结
所以在使用线程池时,其实都是调用的 ThreadPoolExecutor
这个类,只不过传递的不同参数。
这里要特别注意两个参数:
一是
workQueue
的选择,这个就是阻塞队列的选择,如果要说还得这么一大篇文章,之后有机会再写吧。二是
handler
的设置。
那我们发现,在上面的 3 个具体线程池里,其实都没有设定 handler
,这是因为它们都使用了 defaultHandler
。
/*** The default rejected execution handler*/
private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();
在 ThreadPoolExecutor
里有 4 种拒绝策略,这 4 种策略都是 implements
了 RejectedExecutionHandler
:
AbortPolicy
表示拒绝任务并抛出一个异常RejectedExecutionException
。这个我称之为“正式拒绝”,比如你面完了最后一轮面试,最终接到 HR 的拒信。DiscardPolicy
拒绝任务但不吭声。这个就是“默拒”,比如大部分公司拒简历的时候都是默拒。DiscardOldestPolicy
顾名思义,就是把老的任务丢掉,执行新任务。CallerRunsPolicy
直接调用线程处理该任务,就是 VIP 嘛。
所以这 3 种线程池都使用的默认策略也就是第一种,光明正大的拒绝。
好了以上就是本文的所有内容了。当然线程池还有很多知识点,比如 execute()
和 submit()
方法,线程池的生命周期等等。
特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:
长按订阅更多精彩▼如有收获,点个在看,诚挚感谢
新手一看就懂的线程池相关推荐
- 为什么线程池里的方法会执行两次_新手一看就懂的线程池
作者:码农田小齐 来源:https://www.cnblogs.com/nycsde/p/14003888.html 那相信大家也能感受到,其实用多线程是很麻烦的,包括线程的创建.销毁和调度等等,而且 ...
- 基于java洗浴中心管理系统_Java小白也能听懂的线程池的内部原理:老王的洗浴中心...
餐厅的约会 餐盘在灯光的照耀下格外晶莹洁白,女朋友拿起红酒杯轻轻地抿了一小口,对我说:"经常听你说线程池,到底线程池到底是个什么原理?"我楞了一下,心里想女朋友今天是怎么了,怎么突 ...
- 彻底搞懂Java线程池的工作原理
一.线程池的基础知识 创建线程需要占用一定的操作系统资源,在高并发情况下,频繁的创建和销毁线程会大量消耗CPU和内存资源,对程序性能造成很大的影响.为了避免这一问题,Java提供了线程池(通过线程复用 ...
- 苹果手机如何投屏到电视机?新手一看就懂教程
朋友小d在用一部苹果手机,但他觉得屏幕太小了,在这里看视频不太方便,所以他想着要不要换一部屏幕大点的手机.因为预算的问题,他在纠结入手哪一部,我就问他: "其实手机屏幕的大小都差不多,不会有 ...
- python小说爬虫实训报告_python之新手一看就懂的小说爬虫
晚上回来学学爬虫,记住,很多网站一般新手是爬不出来的,来个简单的,往下看: import urllib.request from bs4 import BeautifulSoup #我用的pychar ...
- 数据挖掘的10大算法我用大白话讲清楚了,新手一看就懂
一个优秀的数据分析师,除了要掌握基本的统计学.数据库.数据分析方法.思维.数据分析工具技能之外,还需要掌握一些数据挖掘的思想,帮助我们挖掘出有价值的数据,这也是数据分析专家和一般数据分析师的差距之一. ...
- 通俗易懂的SpringMVC,新手一看就懂!
SpringMVC知识点汇总 MVC模式 MVC模式做了哪些事情呢?总结了下,主要有以下几点: ①将URL映射到java类(Struts2框架)或者java方法(SpringMvc框架)上 ②封装用户 ...
- 新手一看就懂的Spring Bean生命周期
Bean的作用域 在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean.简单地讲,bean就是由IoC容器初始化.装配及管理的对象 几种作用域中,requ ...
- Java中的线程池如何实现,一文彻底搞懂
前言 为什么要用线程池一键获取线程相关资料,还可获取最新java面试真题库 在 HotSpot VM 的线程模型中,Java 线程被一对一映射为内核线程. Java 在使用线程执行程序时,需要调用操作 ...
最新文章
- 【RocketMQ工作原理】消息的消费
- html代码格式化vscode,vscode 代码格式化
- EasyUI中combotree允许多选的时候onSelect事件会重复触发onCheck事件
- 观察者模式在SAP CRM One Order回调函数中的应用
- MobileNet-v3详解
- bzoj千题计划127:bzoj1041: [HAOI2008]圆上的整点
- 升级到jdk1.8后 sun/io/CharToByteConverter错误及处理
- 007 使用SpringMVC开发restful API五--异常处理
- Servlet规范简介
- android timepicker 设置颜色,android TimePicker 踩过的坑 颜色设置
- BZOJ3277 串 【后缀数组】【二分答案】【主席树】
- OEM和ODM的区别
- STM32H743 USART1 LL 库
- android -chrome 调试
- 夜拍王荣耀10 VS同档位旗舰机夜拍功能,实战结果一目了然!
- 冷笑话 企鹅与北极熊
- 论文MICO for MRI bias field estimation and tissue segmentation品讲
- 华为OD机试真题 C++ 实现【预订酒店】【2022.11 Q4 新题】
- 敞开心扉,相互依赖,才可能拥有爱情
- 歌谣:2022年年终总结
热门文章
- android怎么模拟返回,Android中障蔽返回键,HOME键以及模拟HOME键返回效果的方法...
- java获取鼠标在窗口_Java获取窗口鼠标坐标以及键盘按键
- 关于学习Python的一点学习总结(27->关键字参数和默认值)
- 计算机虚拟网络毕业论文,计算机毕业论文——基于WEB的虚拟计算机网络实验平台.doc...
- 制备pdms膜的方法_光栅式PDMS薄膜在透明窗口的超高发射率
- SWPU 2021年团队程序设计天梯赛选拔赛 题解
- 【图论专题】单源最短路的综合应用
- tomcat7修改内存 win_详解Windows下调整Tomcat启动参数的实现方法
- centos6.5下载卸载mysql,centos 6.5卸载Mysql
- 有了Gradle,还会选Maven吗?