害 其实银行就是一个Java线程池
银行就是一个线程池
银行实际上就是一个Java线程池。
一次周末去银行办业务,人多排号,排的久了突然发现银行实际上和Java的线程池如此类似,可以说一模一样。于是我就展开了联想。
- 银行是个线程池
- 周末,银行七个窗口只来了两个值班的员工,那么这两个员工我们就叫做核心员工数
- 那天由于不知道什么原因,来银行办业务的人特别多,两个员工很快就忙不过来了,那么新来的人怎么办呢?只能排个号在休息区的椅子上坐着等,这一排椅子我们就叫做等待队列
- 很快,休息区也坐满了人,这个时候银行的处理业务的效率明显跟不上了,咋办呢?可以叫人会来加班(创建新的线程),由于银行只有7个窗口,所以最终只能有7个员工。最多七个柜台就叫做最大员工数
- 有了新的员工,人多了,处理业务的速度也上来了,会出现两种情况
- 虽然银行已经满负载了,但是人还是源源不断的来银行办业务,这个时候所有的工作人员都忙着,等待区也坐满了人,那么新的人怎么办呢?工作人员可以选择让新的员工明儿,或者一会儿再来,这个让新的人什么时候来的做法,就是一种拒绝策略的选择。
- 除了上面的情况还会有另一种情况,5个人来加班之后,效率提升,很快就把所有的业务做完了,然后银行就没有人办业务了,那么这多出来的五个人要一直在银行待着吗?肯定不是,他们会等一会儿然后回家,这个等一会儿我们可以叫做空闲等待时间
以上银行的流程和线程池的流程完全是一样的,我们只需要将
-
银行工作人员换成线程
-
办业务的人换成任务对象(Runnable)
-
休息区换成等待队列
......
上述的步骤就和线程池的原理,基本参数一模一样了。
说完了线程池的基本原理,接下来就是线程池常见的面试问题。
什么是线程池?有什么好处?
谈到线程池就会想到池化技术,其中最核心的思想就是把宝贵的资源放到一个池子中;每次使用都从里面获取,用完之后又放回池子供其他人使用,有点吃大锅饭的意思。
Java线程池有以下优点:
- 线程是稀缺资源,不能频繁的创建。
- 解耦作用;线程的创建于执行完全分开,方便维护。
- 应当将其放入一个池子中,可以给其他任务进行复用。
创建线程池的方式
- 通过Executors类
- 通过ThreadPoolExecutor类
在Java中,我们可以通过Executors类创建线程池,常见的API有:
- Executors.newCachedThreadPool():无限线程池。
- Executors.newFixedThreadPool(nThreads):创建固定大小的线程池。
- Executors.newSingleThreadExecutor():创建单个线程的线程池。
- Executors.newScheduledThreadPool()
- Executors.newWorkStealingPool(int) java8新增,使用目前机器上可用的处理器作为它的并行级别
以上的这些创建线程池的方法,实际上JDK已经给我们写好的,可以拿来即用的。但是只要我们查看上述方法的源码就会发现:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
以上方法实际上都是利用 ThreadPoolExecutor 类实现的。
所以第二种创建线程方式是自己通过 new ThreadPoolExecutor来进行创建。
Executors 有那么多创建线程池的方法,开发中用哪个比较好?
答案:一个都不用。
从《阿里巴巴Java开发手册》中可以看到
如何通过 ThreadPoolExecutor 自定义线程池?即线程池有哪些重要的参数?
在上一个问题中,我们提到了创建线程池要通过 new ThreadPoolExecutor 的方式,那么,如何创建呢?在创建的时候,又需要哪些参数呢?
我们直接看一下 ThreadPoolExecutor 的构造方法源码,如下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
密密麻麻都是参数,那么这些参数都什么呢?
大致的流程就是
- 创建线程池之后,有任务提交给线程池,会先由 核心线程执行
- 如果任务持续增加,corePoolSize用完并且任务队列满了,这个时候线程池会增加线程的数量,增大到最大线程数
- 这个时候如果任务继续增加,那么由于线程数量已经达到最大线程数,等待队列也已经满了,这个时候线程池实际上是没有能力执行新的任务的,就会采用拒绝策略
- 如果任务量下降,就会有很多线程是不需要的,无所事事,而只要这些线程空闲的时间超过空闲线程时间,就会被销毁,直到剩余线程数为corePoolSize。
通过以上参数可以就可以灵活的设置一个线程池了,示例代码如下:
/** * 获取cpu核心数 */ private static int corePoolSize = Runtime.getRuntime().availableProcessors(); /** * corePoolSize用于指定核心线程数量 * maximumPoolSize指定最大线程数 * keepAliveTime和TimeUnit指定线程空闲后的最大存活时间 */ public static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1000));
线程池底层工作原理?
关于线程池的工作原理和执行流程,通过两张图来进行展示
- 在创建了线程池后,等待提交过来的任务请求。
- 当调用execute()方法添加一个请求任务时,线程池会做如下判断:
- 如果正在运行的线程数量小于corePoolSize,那么马上创建马上创建线程运行这个任务。
- 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列。
- 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务。
- 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
- 当一个线程完成任务时,它会从队列中取下一个任务来执行。
- 当一个线程无事可做超过一定的时间(keepAlilveTime)时,线程池会判断:
- 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
- 所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。
谈谈线程池的饱和策略,也叫做拒绝策略。
所谓饱和策略就是:当等待队列已经排满,再也发不下新的任务的时候,这时,线程池的最大线程数也到了最大值,意味着线程池没有能力继续执行新任务了,这个时候再有新任务提交到线程池,如何进行处理,就是饱和(拒绝)策略
如何合理配置一个线程池
通常我们是需要根据这批任务执行的性质来确定的。
-
IO 密集型任务:由于线程并不是一直在运行,所以可以尽可能的多配置线程,比如 CPU 个数 * 2
-
- IO密集型,即该任务需要大量的IO,即大量的阻塞。
- 在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。
- 所以IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
-
CPU 密集型任务(大量复杂的运算)应当分配较少的线程,比如 CPU 个数相当的大小。CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
当然这些都是经验值,最好的方式还是根据实际情况测试得出最佳配置。
如何关闭线程池
关闭线程池的方法有两个:shutdown()/shutdownNow()
。
shutdown()
执行后停止接受新任务,会把队列的任务执行完毕。shutdownNow()
也是停止接受新任务,但会中断所有的任务,将线程池状态变为 stop。
关闭线程池的代码:
long start = System.currentTimeMillis(); for (int i = 0; i <= 5; i++) { pool.execute(new Job()); } pool.shutdown(); while (!pool.awaitTermination(1, TimeUnit.SECONDS)) { LOGGER.info("线程还在执行。。。"); } long end = System.currentTimeMillis(); LOGGER.info("一共处理了【{}】", (end - start));
pool.awaitTermination(1, TimeUnit.SECONDS)
会每隔一秒钟检查一次是否执行完毕(状态为 TERMINATED
),当从 while 循环退出时就表明线程池已经完全终止了。
❤️ 帅气的你又来看了我
如果你觉得这篇内容对你挺有有帮助的话:
-
点赞支持下吧,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)
-
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。
-
觉得不错的话,也可以关注 编程鹿 的个人公众号看更多文章和讲解视频(感谢大家的鼓励与支持🌹🌹🌹)

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
10条Linux命令锦囊,防你牢底坐穿
http://xjjdog.cn 对200+原创文章进行了细致的分类,阅读更流畅,欢迎收藏。 原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。任何不保留此声明的转载都是抄袭。 每一年,都会有删库跑路的新闻。现实中,删库容易,跑路难,从业者充满了泪水。 这些动作里面,并不总是存在主观的恶意,而是这些命令太危险了。线上操作时,一定要保持清醒的头脑,切记马虎大意。 你说你误操作的,谁信呢? 切记: 严禁酒后登录线上服务器操作 严禁吵架后情绪激动登录线上服务器操作 严禁长时间加班后操作线上环境 禁止在线上试验不熟悉的命令 重要系统先做备份 1. 准备工作 在执行危险命令时,请深呼吸。首先执行ifconfig,或者ip addr命令,确认是在正确的服务器上。 $ipaddr1:lo:<LOOPBACK,UP,LOWER_UP>mtu65536qdiscnoqueuestateUNKNOWNgroupdefaultqlen1000link/loopback00:00:00:00:00:00brd00:00:00:00:00:00inet127.0.0.1/8...
- 下一篇
以 Kubernetes 为代表的容器技术,已成为云计算的新界面
来源 | 阿里巴巴云原生公众号 作者 | 志敏、智清 2020 年 双11,阿里核心系统实现了全面云原生化,扛住了史上最大流量洪峰,向业界传达出了“云原生正在大规模落地”的信号。这里包含着诸多阿里 "云原生的第一次”,其中非常关键的一点是 80% 核心业务部署在阿里云容器 ACK 上,可在 1 小时内扩展超百万容器。 可以说,以 Kubernetes 为代表的容器技术正成为云计算新界面。容器提供了应用分发和交付标准,将应用与底层运行环境进行解耦。Kubernetes 作为资源调度和编排的标准,屏蔽底层架构差异性,帮助应用平滑运行在不同基础设施上。CNCF Kubernetes 的一致性认证,进一步确保不同云厂商 Kubernetes 实现的兼容性,这也让更多的企业愿意采用容器技术来构建云时代的应用基础设施。 云原生容器新界面的崛起 作为容器编排的事实标准,Kubernetes 支持 IaaS 层不同类型的计算、存储、网络等能力,不论是 CPU、GPU、FPGA 还是专业的 ASIC 芯片,都可以统一调度、高效使用异构算力的资源,同时完美支撑各种开源框架、语言和各类型应用。 伴随着 Ku...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,CentOS7官方镜像安装Oracle11G
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Windows10,CentOS7,CentOS8安装Nodejs环境
- MySQL8.0.19开启GTID主从同步CentOS8
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长