为什么线程池里的方法会执行两次_新手一看就懂的线程池

作者:码农田小齐

来源:https://www.cnblogs.com/nycsde/p/14003888.html

那相信大家也能感受到,其实用多线程是很麻烦的,包括线程的创建、销毁和调度等等,而且我们平时工作时好像也并没有这样来 new 一个线程,其实是因为很多框架的底层都用到了线程池。

线程池是帮助我们管理线程的工具,它维护了多个线程,可以降低资源的消耗,提高系统的性能。

并且通过使用线程池,我们开发人员可以更好的把精力放在任务代码上,而不去管线程是如何执行的,实现任务提交和执行的解藕。

本文将从是何、为何、如何的角度来讲解线程池:

  1. 线程池是什么
  2. 为什么要用线程池
  3. 怎么用线程池

线程池 Thread Pool

线程池是一种池化的技术,类似的还有数据库连接池、HTTP 连接池等等。

池化的思想主要是为了减少每次获取和结束资源的消耗,提高对资源的利用率。

比如在一些偏远地区打水不方便的,大家会每段时间把水打过来存在池子里,这样平时用的时候就直接来取就好了。

线程池同理,正是因为每次创建、销毁线程需要占用太多系统资源,所以我们建这么一个池子来统一管理线程。用的时候从池子里拿,不用了就放回来,也不用你销毁,是不是方便了很多?

Java 中的线程池是由 juc 即 java.util.concurrent 包来实现的,最主要的就是 ThreadPoolExecutor 这个类。具体怎么用我们下文再说。

线程池的好处

在多线程的第一篇文章中我们说过,进程会申请资源,拿来给线程用,所以线程是很占用系统资源的,那么我们用线程池来统一管理线程就能够很好的解决这种 资源管理问题

比如因为不需要创建、销毁线程,每次需要用的时候我就去拿,用完了之后再放回去,所以节省了很多资源开销,可以提高系统的运行速度。

而统一的管理和调度,可以合理分配内部资源,根据系统的当前情况调整线程的数量。

那总结来说有以下 3 个好处:

  1. 降低资源消耗:通过重复利用现有的线程来执行任务,避免多次创建和销毁线程。
  2. 提高相应速度:因为省去了创建线程这个步骤,所以在拿到任务时,可以立刻开始执行。
  3. 提供附加功能:线程池的可拓展性使得我们可以自己加入新的功能,比如说定时、延时来执行某些线程。

说了这么多,终于到了今天的重点,我们来看下究竟怎么用线程池吧~

线程池的实现

Java 给我们提供了 Executor 接口来使用线程池。

947f79ad881730ef92e965a46684553f.png

我们常用的线程池有这两大类:

  • ThreadPoolExecutor
  • ScheduledThreadPoolExecutor

它俩的区别呢,就是第一个是普通的,第二个是可以定时执行的。

当然还有其他线程池,比如 JDK 1.7 才出现的 ForkJoinPool ,可以把大任务分割成小任务来执行,最后再大一统。

那么任务提交到一个线程池之后,它会经历一个怎样的过程呢?

执行过程

线程池在内部实际上采用了生产者消费者模型(还不清楚这个模型的在文章开头有改文章的链接)将线程和任务解藕,从而使线程池同时管理任务和线程。

当任务提交到线程池里之后,需要经过以下流程:

608fade338bae5c039b810f047bc07f0.png
  1. 首先它检查 核心线程池 是否已满。这个核心线程池,就是不管用户量多少线程池始终维护的线程的池子。比如说线程池的总容量最多能装 100 个线程,核心线程池我们设置为 50,那么就无论用户量有多少,都保持 50 个线程活着。这个数字当然是根据具体的业务需求来决定的。
  2. 阻塞队列,就是 BlockingQueue ,在生产者消费者这节里提到过。
  3. 最后判断线程池是否已满,就是判断是不是已经有 100 个线程了,而不是 50 个。
  4. 如果已经满了,所以不能继续创建线程了,就需要按照 饱和策略 或者叫做 拒绝策略 来处理。这个饱和策略我们下文再讲。

ThreadPoolExecutor

我们主要说下 ThreadPoolExecutor ,它是最常用的线程池。

153885eae6bc66e49ce0634cf0f9f01e.png

这里我们可以看到,这个类里有 4 个构造方法,点进去仔细看,其实前三个都 call 了最后一个,所以我们只需要看最后一个就好。

public ThreadPoolExecutor(int corePoolSize,                          int maximumPoolSize,                          long keepAliveTime,                          TimeUnit unit,                          BlockingQueue workQueue,                          ThreadFactory threadFactory,                          RejectedExecutionHandler handler) {    ...}

这里我们来仔细看下这几个参数:

  1. corePoolSize :这个就是上文提到过的 核心线程池 的大小,在核心里的线程是永远不会失业的。

corePoolSizethe number of threads to keep in the pool, even if they are idle, unless { @code allowCoreThreadTimeOut} is set

  1. maximumPoolSize :线程池的最大容量。

maximumPoolSize the maximum number of threads to allow in the pool

  1. 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.

  1. unit :对应上面存活时间的时间单位。

unitthe time unit for the { @code keepAliveTime} argument

  1. workQueue :这是一个阻塞队列,其实线程池也是生产者消费者模型的一种,任务 - 相当于生产者,线程 - 相当于消费者,所以这个阻塞队列是用来协调生产和消费的进度的。

workQueue the queue to use for holding tasks before they are executed.

  1. threadFactory :这里用到了工程模式,用来创建线程的。

threadFactory the factory to use when the executor creates a new thread

  1. 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());}

这里我们可以看到,

Integer.MAX_VALUE

它的适用场景在源码里有说:

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();    }}

执行结果:

e0ad51cd68ad3c32e0ce65dc34271b56.png

可以很清楚的看到,线程 1、2、3、5、6 都很快重用了。

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {    return new ThreadPoolExecutor(nThreads, nThreads,                                  0L, TimeUnit.MILLISECONDS,                                  new LinkedBlockingQueue());}

这个线程池的特点是:

  1. 线程池中的线程数量是固定的,也是我们创建线程池时需要穿入的参数;
  2. 超出这个数量的线程就需要在队列中等待。

它的适用场景是:

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();    }}
d3aa3ad3614451cc423aeeb1eeb61bd2.png

这里我限制了线程池里最多有 10 个线程,哪怕有 200 个任务需要执行,也只有 1-10 这 10 个线程可以运行。

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {    return new FinalizableDelegatedExecutorService        (new ThreadPoolExecutor(1, 1,                                0L, TimeUnit.MILLISECONDS,                                new LinkedBlockingQueue()));}

这个线程池顾名思义,里面只有 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();    }}
a9ad6d9fd6aa5a1addb99861d8ee2df0.png

这里在出结果的时候我能够明显的感觉到有些卡顿,这在前两个例子里是没有的,毕竟这里只有一个线程在运行嘛。

小结

所以在使用线程池时,其实都是调用的 ThreadPoolExecutor 这个类,只不过传递的不同参数。

这里要特别注意两个参数:

workQueuehandler

那我们发现,在上面的 3 个具体线程池里,其实都没有设定 handler ,这是因为它们都使用了 defaultHandler 。

/** * The default rejected execution handler */private static final RejectedExecutionHandler defaultHandler =    new AbortPolicy();

在 ThreadPoolExecutor 里有 4 种拒绝策略,这 4 种策略都是 implements 了 RejectedExecutionHandler :

  1. AbortPolicy 表示拒绝任务并抛出一个异常 RejectedExecutionException 。这个我称之为“正式拒绝”,比如你面完了最后一轮面试,最终接到 HR 的拒信。
  2. DiscardPolicy 拒绝任务但不吭声。这个就是“默拒”,比如大部分公司拒简历的时候都是默拒。
  3. DiscardOldestPolicy 顾名思义,就是把老的任务丢掉,执行新任务。
  4. CallerRunsPolicy 直接调用线程处理该任务,就是 VIP 嘛。

所以这 3 种线程池都使用的默认策略也就是第一种,光明正大的拒绝。

好了以上就是本文的所有内容了。当然线程池还有很多知识点,比如 execute() 和 submit() 方法,线程池的生命周期等等。

但随着阅读量的逐渐走低,齐姐意识到了这似乎有什么误会,所以这篇文章是多线程系列的最后一篇了。

本文已收录至我的 Github 上: https://github.com/xiaoqi6666/NYCSDE

作者:码农田小齐

来源:https://www.cnblogs.com/nycsde/p/14003888.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/556322.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为鸿蒙热水器,美的华为跨界联合!搭载鸿蒙OS的美的产品双11上市

【宅秘新闻】在前段时间的华为开发者大会上&#xff0c;华为正式发布了鸿蒙OS 2.0。华为消费者业务软件部总裁王成录当时表示&#xff0c;美的等家电品牌很快就会推出基于鸿蒙OS 2.0的家电产品。11月11日&#xff0c;美的集团官方宣布搭载鸿蒙OS的美的产品在双十一惊喜上市。宅…

Eclipse构建Maven分包分模块项目并构建服务端

首先说一下Maven 模块结构&#xff1a; 一个简单的Maven模块结构是这样的&#xff1a; ---- app-parent 一个父项目(app-parent)聚合很多子项目(app-util,app-dao,app-service,app-web) |---- pom.xml (pom) | |-------- app-util | |-------- pom.xml (jar) | |-------- app-…

我们一起动手学大模型应用开发

大模型正逐步成为信息世界的新革命力量&#xff0c;其通过强大的自然语言理解、自然语言生成能力&#xff0c;为开发者提供了新的、更强大的应用开发选择。 随着国内外井喷式的大模型 API 服务开放&#xff0c;如何基于大模型 API 快速、便捷地开发具备更强能力、集成大模型的…

登陆页面html5代码qq,HTML5QQ登录的一篇代码分享

html5背景*{margin:0;padding:0;list-style-type:none;}a,img{border:0;}$(function () {if (!window.ActiveXObject && !!document.createElement("canvas").getContext) {$.getScript("http://im-img.qq.com/pcqq/js/200/cav.js?_1428576021379"…

unity 多台 显示器 控制_飞利浦292E2E评测丨宽屏显示器中的多面手

■本文来自中关村在线屏幕比例21:9或32:9的显示器&#xff0c;因为横向屏幕很长&#xff0c;所以被人形象的称为带鱼屏。相较于16:9的显示器&#xff0c;屏幕拥有更多显示面积&#xff0c;我们能获得更多信息&#xff0c;大大提升办公效率。同时带鱼屏打游戏和看电影时也能获得…

Idea 创建简单的SpringBoot 父子项目

背景 使用Idea 创建一个模块化的SpringBoot项目&#xff0c;但是发现Idea 创建父子项目的方式较Eclipse 较为不同&#xff0c;且Idea 创建的过程较Eclipse创建父子项目的过程复杂。 Eclipse 创建SpringBoot父子项目传送门 网上虽然有Idea创建SpringBoot父子项目&#xff0c;但…

微服务架构与SpringCloud

微服务架构简述 通常而言&#xff0c;微服务架构是一种架构模式或者说是一种架构风格&#xff0c;它提倡将单一应用程序划分成一组小的服务&#xff0c;每个服务运行在其独立的自己的进程中&#xff0c;服务之间互相协调、互相配合为用户提供最终价值。服务之间采用轻量级的通…

REST和RPC是什么东东?两者有什么区别

1 REST与RPC概念 什么是REST REST是一种架构风格&#xff0c;指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。REST规范把所有内容都视为资源&#xff0c;网络上一切皆资源。 REST并没有创造新的技术&#xff0c;组件或服务&#xff0c…

简述计算机进行远程桌面管理操作过程,服务器应用之“远程桌面连接”(操作篇)...

在上篇《服务器应用之“远程桌面连接”(部署篇) 》《服务器应用之“远程桌面连接”(建立篇) 》&#xff0c;我们介绍了远程桌面的部署和建立方法。在远程桌面连接成功后就可以进行一些远程桌面连接可以进行的计算机管理了。具体可以进行的远程桌面连接操作如下&#xff1a;1. 使…

未为dll加载任何符号_专家发现aspersky 和Trend Micro安全性解决方案中的DLL劫持问题...

SafeBreach的研究人员发现了Kaspersky安全连接、Trend Micro最大安全性和Autodesk桌面应用程序中的几个DLL劫持漏洞&#xff0c;黑客可以利用这些漏洞进行DLL预加载、代码执行和权限升级。第一个问题在卡巴斯基安全连接(KSDE) VPN客户端&#xff0c;跟踪为CVE-2019-15689&#…

什么是Eureka? 单机版Eureka如何使用?

Eureka 是什么&#xff1f; Eureka 是Spring Cloud的服务治理组件&#xff0c;有三个核心角色&#xff1a; 服务注册中心、服务提供者、服务消费者。Eureka 主管服务注册中心。 是Netflix的一个子模块&#xff0c;也是核心模块之一。Eureka是一个基于REST的服务&#xff0c;用…

ntko跨浏览器插件_继泄露版后,微软全新 Chrome 内核 Edge 浏览器你都测试过了吗?...

尽管微软的 Office 和 Windows 10 势头不错&#xff0c;但像 WP 手机、XBox 等方面似乎并未获得预期的成功。而先前为了替代老旧的 IE 浏览器&#xff0c;微软倾力开发的 Edge 也未见起色。不过微软也一直在努力变得更加开放&#xff0c;比如发布 VS for Mac、Office for Mac、…

如何将单机版的Eureka服务改为集群版Eureka服务

Eureka 集群原理 基本原理 上图是来自eureka的官方架构图&#xff0c;这是基于集群配置的eureka&#xff1b; 处于不同节点的eureka通过Replicate进行数据同步Application Service为服务提供者Application Client为服务消费者Make Remote Call完成一次服务调用 服务启动后向…

台式计算机读不到u盘怎么回事,电脑读不出u盘怎么办

把U盘插入电脑USB接口中&#xff0c;却发现找不到U盘图标&#xff0c;甚至连安装U盘驱动的提示也没有&#xff0c;那么电脑读不出u盘怎么办呢?天学习啦小编就和大家说说电脑读不出u盘的解决方法。电脑读不出u盘解决方法1、首先应该排除是否是由于电脑系统问题&#xff0c;具体…

Eureka 与Zookeeper 的区别,Eureka相较于Zookeeper好在哪?

传统的ACID A&#xff08;Atomicity&#xff09; 原子性C&#xff08;Consistency&#xff09; 一致性I &#xff08;Isolation&#xff09;独立性D&#xff08;Durability&#xff09;持久性 关系型数据库&#xff08;MySQL&#xff0c;Oracle&#xff0c;SqlServer&#xf…

如何将Springboot项目成功部署到linux服务器上?

springboot的jar包方式 idea默认就是jar打包方式所以直接使用maven工具按照步骤点击就可以直接打包ps:打包前你的数据库相关的连接信息要记得修改&#xff0c;不能再用本地的了 第二步&#xff1a;Maven clean &#xff1a;清除编译后的目录&#xff0c;默认是target目录 [IN…

2018年3月计算机二级考试题,2018年3月计算机二级考试Access综合试题十

2018年3月计算机等级考试即将开始&#xff0c;小编在这里为考生们整理了2018年3月计算机二级考试Access综合试题&#xff0c;希望能帮到大家&#xff0c;想了解更多资讯&#xff0c;请关注出国留学网的及时更新哦。2018年3月计算机二级考试Access综合试题十(1)要将“选课成绩”…

sqldataadapter.fill 索引超出了数组界限_小学生学习C++||第十五节 数组

存储数据的方式第一种&#xff0c;变量&#xff1a;存放1个学生的数学成绩可以定义1个变量存放&#xff0c;那么如果想要存放100个学生的数学成绩应该如何存储呢&#xff1f;第二种&#xff0c;数组&#xff1a;引入数组概念&#xff1a;可以用来表达类型相同的元素的集合&…

数据字典在sga的哪一个组件中缓存_【赵强老师】Oracle数据库的内存结构

首先&#xff0c;我们通过一张图片来了解一下Oracle数据库的内存结构&#xff0c;如下&#xff1a;每个数据库实例有两个关联的内存结构—系统全局区(SGA),程序全局区(PGA)。系统全局(SGA)&#xff1a;一组共享的内存结构(称为SGA 组件)&#xff0c;其中包含一个OracleDB 实例的…

计算机黑屏策略,小黑w7系统诊断策略服务已被禁用的还原教程

小黑w7家庭版开机速度快&#xff0c;是入门级小黑w7系统。据调查不少用户在使用中都碰到了诊断策略服务已被禁用的问题。面对小黑w7系统诊断策略服务已被禁用这样的小问题&#xff0c;我们该如何解决呢&#xff1f;不经常使用电脑的朋友对于小黑w7系统诊断策略服务已被禁用的问…