动态调整线程池_调整线程池的重要性

动态调整线程池

无论您是否知道,您的Java Web应用程序很可能都使用线程池来处理传入的请求。 这是许多人忽略的实现细节,但是迟早您需要了解如何使用该池以及如何为您的应用程序正确调整池。 本文旨在说明线程模型,线程池是什么以及正确配置线程池所需执行的操作。

单螺纹

让我们从一些基础知识开始,并随着线程模型的发展而前进。 无论您使用哪种应用程序服务器或框架, Tomcat , Dropwizard , Jetty ,它们都使用相同的基本方法。 一个深埋在Web服务器内部的套接字。 该套接字正在侦听传入的TCP连接,并接受它们。 一旦接受,就可以从新建立的TCP连接中读取数据,进行解析并将其转换为HTTP请求。 然后将此请求移交给Web应用程序,以完成其所需的操作。

为了理解线程的作用,我们将不使用应用程序服务器,而是从头开始构建一个简单的服务器。 该服务器反映了大多数应用程序服务器的功能。 首先,单线程Web服务器可能如下所示:

ServerSocket listener = new ServerSocket(8080);
try {while (true) {Socket socket = listener.accept();try {handleRequest(socket);} catch (IOException e) {e.printStackTrace();}}
} finally {listener.close();
}

此代码在端口8080上创建一个ServerSocket ,然后在紧密循环中ServerSocket检查要接受的新连接。 接受后,套接字将传递给handleRequest方法。 该方法通常会读取HTTP请求,执行所需的任何过程并编写响应。 在此简单示例中,handleRequest读取一行,并返回简短的HTTP响应。 handleRequest做一些更复杂的事情是正常的,例如从数据库中读取或进行某种其他类型的IO。

final static String response =“HTTP/1.0 200 OK\r\n” +“Content-type: text/plain\r\n” +“\r\n” +“Hello World\r\n”;public static void handleRequest(Socket socket) throws IOException {// Read the input stream, and return “200 OK”try {BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));log.info(in.readLine());OutputStream out = socket.getOutputStream();out.write(response.getBytes(StandardCharsets.UTF_8));} finally {socket.close();}
}

由于只有一个线程处理所有接受的套接字,因此在接受下一个请求之前,必须完全处理每个请求。 在实际的应用程序中,等效的handleRequest方法返回大约100毫秒是正常的。 如果是这种情况,服务器将被限制为每秒仅处理10个请求,一个接一个。

多线程

即使handleRequest可能在IO上被阻止,CPU也可以自由处理更多请求。 使用单线程方法是不可能的。 因此,可以通过创建多个线程来改进此服务器以允许并发操作:

public static class HandleRequestRunnable implements Runnable {final Socket socket;public HandleRequestRunnable(Socket socket) {this.socket = socket;}public void run() {try {handleRequest(socket);} catch (IOException e) {e.printStackTrace();}}
}ServerSocket listener = new ServerSocket(8080);
try {while (true) {Socket socket = listener.accept();new Thread(new HandleRequestRunnable(socket)).start();}
} finally {listener.close();
}

在这里,仍然在单个线程内的紧密循环中调用accept(),但是一旦接受TCP连接并且有可用的套接字,就会产生一个新线程。 这个产生的线程执行一个HandleRequestRunnable,它从上面简单地调用相同的handleRequest方法。

创建新线程后,现在可以释放原始的accept()线程来处理更多的TCP连接,并允许应用程序同时处理请求。 该技术被称为“每个请求线程”,是最流行的方法。 值得注意的是,还有其他方法,例如事件驱动的异步模型NGINX和Node.js部署,但是它们不使用线程池,因此不在本文讨论范围之内。

在每个请求线程数方法中,创建新线程(然后销毁它)可能会很昂贵,因为JVM和OS都需要分配资源。 另外,在上述实现中,正在创建的线程数不受限制。 不受限制是很成问题的,因为它会很快导致资源枯竭。

资源枯竭

每个线程都需要一定数量的内存用于堆栈。 在最新的64位JVM上, 默认堆栈大小为1024KB。 如果服务器收到大量请求,或者handleRequest方法变慢,则服务器可能会出现大量并发线程。 因此,要管理1000个并发请求,仅1000个线程将消耗1GB的JVM RAM,仅用于线程的堆栈。 另外,在每个线程中执行的代码将在处理请求所需的堆上创建对象。 这很快就会加起来,并且可能超过分配给JVM的堆空间,从而对垃圾收集器施加压力,导致崩溃并最终导致OutOfMemoryErrors 。

线程不仅消耗RAM,而且可能使用其他有限资源,例如文件句柄或数据库连接。 超过这些可能导致其他类型的错误或崩溃。 因此,为了避免耗尽资源,重要的是避免无限制的数据结构。

不是万能的,但是可以通过使用-Xss标志调整堆栈大小来缓解堆栈大小问题。 较小的堆栈将减少每个线程的开销,但可能导致StackOverflowErrors 。 您的里程会有所不同,但是对于许多应用程序,默认的1024KB过多,而更小的256KB或512KB的值可能更合适。 Java允许的最小值是16KB。

线程池

为了避免连续创建新线程并限制最大数量,可以使用一个简单的线程池。 简而言之,该池跟踪所有线程,在需要达到上限时创建新线程,并在可能的情况下重用空闲线程。

ServerSocket listener = new ServerSocket(8080);
ExecutorService executor = Executors.newFixedThreadPool(4);
try {while (true) {Socket socket = listener.accept();executor.submit( new HandleRequestRunnable(socket) );}
} finally {listener.close();
}

现在,此代码不是直接创建线程,而是使用ExecutorService,它提交要在线程池中执行的工作(用Runnables术语)。 在此示例中,四个线程的固定线程池用于处理所有传入的请求。 这限制了“进行中”请求的数量,因此限制了资源的使用。

除了newFixedThreadPool之外 ,Executors实用程序类还提供了newCachedThreadPool方法。 这受到较早的无限线程数量的困扰,但是只要有可能,就利用先前创建但现在空闲的线程。 通常,这种类型的池对于不阻塞外部资源的短暂请求很有用。

ThreadPoolExecutors可以直接构造,从而可以自定义其行为。 例如,可以定义池中线程的最小和最大数量,以及何时创建和销毁线程的策略。 简短的例子。

工作队列

在固定线程池的情况下,细心的读者可能想知道如果所有线程都忙,并且有新的请求进入,该怎么办。那么ThreadPoolExecutor使用队列来保存线程可用之前的待处理请求。 默认情况下,Executors.newFixedThreadPool和Executors.newCachedThreadPool都使用无界LinkedList。 同样,这会导致资源耗尽问题,尽管速度要慢得多,因为每个排队的请求都小于一个完整的线程,并且通常不会使用那么多资源。 但是,在我们的示例中,每个排队的请求都持有一个套接字(取决于操作系统)将占用一个文件句柄。 这是操作系统将限制的资源,因此除非有必要,否则最好不要保留它。 因此,限制工作队列的大小也很有意义。

public static ExecutorService newBoundedFixedThreadPool(int nThreads, int capacity) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(capacity),new ThreadPoolExecutor.DiscardPolicy());
}public static void boundedThreadPoolServerSocket() throws IOException {ServerSocket listener = new ServerSocket(8080);ExecutorService executor = newBoundedFixedThreadPool(4, 16);try {while (true) {Socket socket = listener.accept();executor.submit( new HandleRequestRunnable(socket) );}} finally {listener.close();}
}

再次,我们创建了一个线程池,但是我们没有使用Executors.newFixedThreadPool帮助器方法,而是自己创建了ThreadPoolExecutor,并传递了一个限制为16个元素的有界LinkedBlockingQueue 。 或者,可以使用ArrayBlockingQueue ,它是有界缓冲区的实现。

如果所有线程都忙,并且队列已满,则下一步将由ThreadPoolExecutor的最后一个参数定义。 在此示例中,使用了DiscardPolicy ,它只是丢弃将使队列溢出的所有工作。 还有其他政策,如AbortPolicy它抛出一个异常,或CallerRunsPolicy执行该调用者的线程上的工作。 此CallerRunsPolicy提供了一种简单的方法来自我限制可以添加作业的速率,但是,这可能是有害的,阻塞了应保持不受阻塞的线程。

一个好的默认策略是“放弃”或“中止”,这两者都会放弃工作。 在这些情况下,很容易将简单错误返回给客户端,例如HTTP 503“服务不可用” 。 有人会争辩说只是增加队列大小,然后所有工作最终都会运行。 但是,用户不愿永远等待,如果从根本上说工作进入的速度超过了可以执行的速度,那么队列将无限期地增长。 相反,该队列仅应用于消除突发请求,或处理处理中的短暂停顿。 在正常操作中,队列应为空。

有多少个线程?

现在我们了解了如何创建线程池,困难的问题是应该有多少个线程可用? 我们确定最大数量应该限制为不导致资源耗尽。 这包括所有类型的资源,内存(堆栈和堆),打开的文件句柄,打开的TCP连接,远程数据库可以处理的连接数以及任何其他有限资源。 相反,如果线程是与CPU绑定而不是与IO绑定,则应将物理核的数量视为有限,并且每个核最多只能创建一个线程。

这一切都取决于应用程序正在执行的工作。 用户应使用各种池大小以及实际的请求混合来运行负载测试。 每次增加它们的线程池大小直到断点。 这样就可以在资源耗尽时找到上限。 在某些情况下,明智的做法是增加可用资源的数量,例如为JVM提供更多的RAM,或者调整OS以允许更多的文件句柄。 但是,在某个时候会达到理论上限,应该注意,但这还不是故事的结局。

利特尔定律

Littlelaw

排队论,尤其是利特尔定律 ,可以用来帮助理解线程池的属性。 简单来说,利特尔定律描述了三个变量之间的关系。 L进行中的请求数,λ新请求到达的速率,W平均处理该请求的时间。 例如,如果每秒有10个请求到达,并且每个请求花费一秒钟的时间来处理,则在任何时间平均有10个正在进行的请求。 在我们的示例中,这映射为使用10个线程。 如果处理单个请求的时间增加了一倍,则运行中的平均请求数也将增加一倍,达到20,因此需要20个线程。

了解执行时间对进行中的请求的影响非常重要。 某些后端资源(例如数据库)停顿是很常见的,导致请求花费更长的时间来处理,从而很快耗尽了线程池。 因此,理论上限可能不是池大小的适当限制。 相反,应该对执行时间设置一个限制,并与理论上限结合使用。

例如,假设在JVM超出其内存分配之前,可以处理的最大传输中请求为1000。 如果我们预算每个请求的时间不超过30秒,那么我们应该期望在最坏的情况下每秒处理不超过33个请求。 但是,如果一切正常,并且请求仅用500毫秒即可处理,则应用程序每秒只能在1000个线程上处理2000个请求。 指定可以使用队列来消除短暂的延迟突发也可能是合理的。

为什么要麻烦?

如果线程池中的线程太少,则存在以下风险:资源利用不足,并不必要地将用户拒之门外。 但是,如果允许太多线程,则会发生资源耗尽,这可能会造成更大的破坏。

不仅会耗尽本地资源,还可能对其他资源产生不利影响。 例如,多个应用程序查询同一个后端数据库。 数据库通常对并发连接数有硬性限制。 如果一个行为异常的无限制应用程序消耗了所有这些连接,它将阻止其他应用程序访问数据库。 造成大范围的中断。

更糟糕的是,可能会发生级联故障。 想象一下一个环境,其中有一个应用程序的多个实例,位于一个公共负载平衡器的后面。 如果由于过多的正在进行中的请求而使其中一个实例的内存不足,则JVM将花费更多时间进行垃圾收集,并减少处理请求的时间。 这种减慢速度将降低该实例的容量,并迫使其他实例处理更高比例的传入请求。 随着他们现在使用无限制的线程池处理更多请求,会发生相同的问题。 它们耗尽了内存,然后再次开始积极地进行垃圾收集。 这个恶性循环在所有实例之间级联,直到出现系统性故障。

我经常观察到没有进行负载测试,并且允许任意数量的线程。 在通常情况下,应用程序可以使用少量线程以传入速率愉快地处理请求。 但是,如果处理请求取决于远程服务,并且该服务暂时变慢,则W的增加(平均处理时间)的影响会很快耗尽池。 由于从未对应用程序进行最大数量的负载测试,因此会出现之前概述的所有资源耗尽问题。

多少个线程池?

在微 服务或面向服务的体系结构 (SOA)中,访问多个远程后端服务是正常的。 此设置特别容易发生故障,因此应仔细解决这些问题。 如果远程服务的性能下降,则可能导致线程池Swift达到其极限,从而丢弃后续请求。 但是,并非所有请求都可能需要此不正常的后端,但是由于线程池已满,因此这些请求被不必要地删除了。

通过提供特定于后端的线程池,可以隔离每个后端的故障。 在这种模式下,仍然只有一个请求工作程序池,但是如果请求需要调用远程服务,则工作将转移到该后端的线程池。 这使主请求池不会受到单个缓慢后端的负担。 这样,只有需要特定后端池的请求才会在故障时受到影响。

多个线程池的最后一个好处是,它有助于避免某种形式的死锁。 如果由于尚未处理的请求而导致每个可用线程都被阻塞,则将发生死锁,并且没有线程能够前进。 当使用多个池并充分了解它们执行的工作时,可以在某种程度上缓解此问题。

截止日期和其他最佳做法

常见的最佳做法是确保所有远程呼叫都有最后期限。 也就是说,如果远程服务在合理时间内没有响应,则该请求将被放弃。 可以在线程池中使用相同的技术。 具体来说,如果线程正在处理一个请求的时间超过了定义的期限,则应终止该线程。 为新请求腾出空间,并在W上设置上限。这似乎是一种浪费,但是如果用户(通常可能是Web浏览器)正在等待响应,则30秒后,浏览器可能只会给出无论如何,还是用户可能变得不耐烦并导航离开。

快速失败是在为后端创建池时可以采用的另一种方法。 如果后端发生故障,则线程池将Swift填充等待连接到无响应后端的请求。 相反,可以将后端标记为不正常,所有后续请求都可能立即失败,而不是不必要地等待。 但是请注意,需要一种机制来确定后端何时再次恢复健康。

最后,如果一个请求需要独立地调用多个后端,则应该可以并行而不是顺序地调用它们。 这将减少等待时间,但以增加线程为代价。

幸运的是,有一个很棒的库hystrix ,它打包了许多这些最佳实践,并以简单安全的方式公开了它们。

结论

希望本文能增进您对线程池的了解。 通过了解应用程序的需求,并结合使用最大线程数和平均响应时间,可以确定适当的线程池。 这不仅可以避免级联故障,而且可以帮助计划和配置服务。

即使您的应用程序可能未显式使用线程池,但它们还是被应用程序服务器或更高级别的抽象隐式使用。 Tomcat , JBoss , Undertow , Dropwizard都为其线程池(执行servlet的池)提供了多个可调参数。

翻译自: https://www.javacodegeeks.com/2015/12/importance-tuning-thread-pools.html

动态调整线程池

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

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

相关文章

java系统架构师有的特质_Java中特质模式的定义

java系统架构师有的特质在本文中&#xff0c;我将介绍特征的概念&#xff0c;并为您提供一个具体示例&#xff0c;说明如何在Java中使用它们以减少对象设计中的冗余。 我将首先提出一个虚构的案例&#xff0c;其中可以使用特征来减少重复&#xff0c;然后以使用Java 8的特征模式…

tdd java_Java TDD简介–第2部分

tdd java再次问好&#xff01; 在上一篇博客文章中&#xff0c;我在没有紧密引用Java的情况下总体上解释了TDD理论 &#xff0c;但是在这一部分中&#xff0c;我们开始进行TDD实践。 我们的目标是遍历TDD的所有阶段&#xff1a;从需求分析到测试代码的重构。 我们将在具有Java&…

计算机怎么没有桌面文件夹名称大全,你的电脑文件总是找不到?是时候学习文件夹分类整理了...

我们常常说做好办公整理&#xff0c;我们工作起来才更有效率&#xff1b;此处的办公整理不单单是对办公桌整理整齐&#xff0c;电脑里面的文件也需要定期整理&#xff0c;你有没有发现常常找一个文件的时候却总是找不到&#xff1f;那么此时就应该学习文件夹分类整理了&#xf…

selenide_使用Selenide进行有效的UI测试

selenide等待奇迹 圣诞节是奇迹的时刻。 在新的一年的前夕&#xff0c;我们都会制定下一个计划。 我们希望所有问题都将在最后一年消失&#xff0c;在来年出现奇迹。 每个Java开发人员都梦想着奇迹&#xff0c;使他成为世界上最有效的Java开发人员。 我想向你展示这样的奇迹…

win10无法更改计算机设置,Win10“无法保存ip设置,请检查一个或多个设置并重试”的解决方法...

近期有网友想要在Win10系统中手动设置本机IP地址&#xff0c;但是填写完毕之后却提示了“无法保存ip设置,请检查一个或多个设置并重试”&#xff0c;那么遇到这个问题&#xff0c;我们要如何解决呢&#xff1f;下面装机之家分享一下Win10“无法保存ip设置,请检查一个或多个设置…

24段魔尺拼图指南_拼图项目动手指南

24段魔尺拼图指南Jigsaw项目将把模块化引入Java平台&#xff0c;根据原始计划&#xff0c;它将在12月10日完成功能。 所以我们在这里&#xff0c;但拼图在哪里&#xff1f; 在过去的六个月中肯定发生了很多事情&#xff1a; 原型问世 &#xff0c;内部API的迫在眉睫的删除引起…

sql 解析 java_将Java 8流解析为SQL

sql 解析 java当Java 8发行并且人们开始流式处理各种东西时&#xff0c;不久之后他们就开始想象如果可以以相同的方式使用数据库将有多大的潜力。 本质上&#xff0c;关系数据库由以表状结构组织的巨大数据块组成。 这些结构非常适合进行过滤和映射操作&#xff0c;如SQL语言的…

杭州电子科技大学保研计算机,杭州电子科技大学计算机学院计算机科学与技术(一级学科)保研细则...

杭州电子科技大学计算机学院计算机科学与技术(一级学科)保研细则信息&#xff0c;是考研之前需要获取相应的考研信息&#xff0c;比如考试大纲、招考专业、招考目录等等基本信息&#xff0c;这些内容是进行考研前期工作的必要准备。考生可以从各院校的研招网进行查询&#xff0…

返回路径平面上的间隙_裁切机的上刀下刀如何调整?

每天都在使用裁切机时&#xff0c;由于各种因素往往会出现上刀、下刀现象(也称凸刀、凹刀现象)&#xff0c;给后面的套准带来困难。在此小编对上刀、下刀现象的产生原因做一分析与总结。01千斤压力不够(1)机械压力弹簧弹力不够。此时必须增大压力&#xff0c;如果增大到最大位置…

pcb板材的tg是什么_做到这6点,PCB过回焊炉不会出现板弯及板翘!

【维文信PCBworld】在PCB板子过回焊炉容易发生板弯及板翘&#xff0c;大家都知道&#xff0c;那么如何防止PCB板子过回焊炉发生板弯及板翘&#xff0c;下面就为大家阐述下&#xff1a;1.降低温度对PCB板子应力的影响既然「温度」是板子应力的主要来源&#xff0c;所以只要降低回…

java-ee-api_刷新器-Java EE 7概览

java-ee-api随着红帽JBoss企业应用平台7&#xff08;EAP 7&#xff09;的迫在眉睫&#xff0c;甚至WebLogic Server刚刚获得Java EE 7认证&#xff0c;我认为现在应该是时候对Java Enterprise Edition 7进行一些更新了。功能&#xff0c;并指向更多资源。 Java EE 7 –开发人员…

层 数据仓库_小尝试:基于指标体系的数据仓库搭建和数据可视化

关于作者&#xff1a;小姬&#xff0c;某知名互联网公司产品专家&#xff0c;对数据采集、生产、加工有所了解&#xff0c;期望多和大家交流数据知识&#xff0c;以数据作为提出好问题的基础&#xff0c;挖掘商业价值。0x00 前言我将整理文章分享数据工作中的经验&#xff0c;因…

计算机中级职称报考入口,2020年9月计算机职称考试报名流程(附入口)

2020年9月计算机等级报名公告正在陆续公布&#xff0c;计算机等级考试网上怎么报名&#xff1f;小编为大家整理计算机等级报名流程如下&#xff1a;2020年9月计算机等级报名流程一、注册通行证考生登入报名网页后&#xff0c;点击“用户注册”按钮&#xff0c;根据网页提示&…

简单工厂抽象工厂工厂方法_让工厂美丽

简单工厂抽象工厂工厂方法每个名副其实的Java程序员都知道Factory Pattern 。 这是一种便捷&#xff0c;标准化的方法&#xff0c;它通过教一个组件如何捕鱼而不是将它们交给它们来减少耦合。 但是&#xff0c;在使用大型系统时&#xff0c;该模式确实会向系统添加很多样板代码…

内蒙古大学计算机组成原理实验,内蒙古大学计算机组成原理期末练习0

内蒙古大学计算机组成原理期末练习0 (4页)本资源提供全文预览&#xff0c;点击全文预览即可全文预览,如果喜欢文档就下载吧&#xff0c;查找使用更方便哦&#xff01;9.9 积分第 1 页 共 4 页 《《计算机组成原理计算机组成原理》》期末练习期末练习 班级 专业 姓名 学号 编号 …

dev的编辑器不支持getchar吗_“两头婚兴起”:你支持不娶不嫁,孩子随父姓也随母姓吗?...

何为两头婚&#xff1f;简单来说就是男不娶&#xff0c;女不嫁&#xff0c;没有彩礼也没有嫁妆的说法。男女俩人结婚之后没有谁到谁家里之说&#xff0c;两夫妻过自己的小日子&#xff0c;与各自的原生家庭保持一定的联系。婚后生育两个孩子&#xff0c;一个随父姓&#xff0c;…

全国计算机证件照要露耳朵吗,结婚证照片要露耳朵么 结婚登记照露一只耳朵行吗...

结婚证件照&#xff0c;这是可以在在民政局里面照的&#xff0c;也是可以在外面的影楼照的&#xff0c;在外面影楼照的话&#xff0c;照出来会比较好看&#xff0c;就是价格有点贵。结婚证上面的照片是证件照&#xff0c;所以是有一定的要求的。露一只耳朵行不行呢&#xff1f;…

findfirst_当心findFirst()和findAny()

findfirst过滤Java 8 Stream &#xff0c;通常使用findFirst()或findAny()来获取在过滤器中幸存的元素。 但这可能并不能真正实现您的意思&#xff0c;并且可能会出现一些细微的错误。 那么 从我们的Javadoc&#xff08; 此处和此处 &#xff09;可以看出&#xff0c;这两种方…

java流式传输对象_使用Java 8在地图上流式传输

java流式传输对象在本文中&#xff0c;我将向您展示如何在标准Java映射上有效地实现Speedment Open Source流&#xff0c;并将Stream接口扩展为MapStream&#xff01; 即使在复杂的情况下&#xff0c;此添加将使保持流的具体性和可读性变得更加容易。 希望这将允许您继续流式传…

oracle11g支持xp吗_拔掉U盘前一定需要安全弹出吗?

相信每一个使用过U盘的人&#xff0c;都经历过不安全弹出USB设备就直接拔掉&#xff0c;然后被电脑无情提示没有正确弹出USB设备的状况。拔掉U盘前点击安全弹出&#xff0c;已经成了一种默认的操作。那么&#xff0c;问题来了&#xff0c;拔掉U盘前真的需要安全弹出吗&#xff…