javaandroid 线程池池常驻android 线程池占内存吗

出处:http://coderbee.net 现状
业务部门反应网站访问特别慢,负责运维监控的同事说MQ消息队列积压了,中间件的说应用服务器内存占用很高,GC 一直回收不了内存,GC 线程占了近 100% 的 CPU,其他的基本上都在等待,数据库很正常,完全没压力。没啥办法,线程、堆 dump 出来后,重启吧,然后应用又正常了。
这种故障之前其实也碰到过了,分析了当时 dump 出来的堆后发现,处理 MQ 消息的线程池的队列长度达百万级别,占用了超过 1.3G
内存,这些内存都是没法回收的。
程序的实现目前是这样的:关联系统把消息推送到 MQ 上,我们再从 MQ 上拉消息下来处理;每种类型的消息都有一个线程负责从 MQ 上拉消息,拉下来后封装成线程池的任务提交给相应的线程池去执行。代码可以简化为:
package net.coderbee.mq.
import java.util.concurrent.ExecutorS
import java.util.concurrent.E
public class MQListener {
public ExecutorService executor = Executors.newFixedThreadPool(8);
public void onMessage(final Object message) {
executor.execute(new Runnable() {
public void run() {
// 耗时且复杂的消息处理逻辑
complicateHanlde(message);
private void complicateHanlde(Object message) {
这个实现就是导致故障的根源,
Executors.newFixedThreadPool(8) 创建的线程池的任务队列是无边界的:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue&Runnable&());
当时是关联系统出故障了,他们恢复后,往 MQ 里狂推消息,我们系统里面的 MQListener 不断地从 MQ 拉消息下来,直接塞进线程池里,由于线程池处理消息的速度远远慢于消息进入的速度,所以线程池的队列不断增长,直到把所有的堆内存都占用了,这时不断引发 FullGC,但每次 FullGC 都没法回收到内存,应用也就挂死在那了。
之前那次故障也是线程池队列积压导致的,引起的原因是消息处理逻辑调用了外部接口,由于外部接口的响应非常慢,严重拖慢了消息的处理进度,改成异步调用之后好了些。但问题的根源并没有解决,就像昨天关联系统狂推消息后,我们的系统还是挂了。
我的思路其实很简单,MQ 是用来系统间解耦的,也是一个缓冲,目前的实现是把处理消息的线程池又用作一个 MQ 了,消息不能不受控地进入线程池的任务队列,所以,要换成使用定长的阻塞队列,队列满了就暂停拉取消息。把线程池替换成:
private int nThreads = 8;
private int MAX_QUEUQ_SIZE = 2000;
private ExecutorService executor = new ThreadPoolExecutor(nThreads,
nThreads, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue&Runnable&(MAX_QUEUQ_SIZE),
new ThreadPoolExecutor.CallerRunsPolicy());
把线程池队列满的时候直接让调用者(也就是
MQListener)执行任务,这样即延缓了消息拉取的速度,当
MQListener 再去拉取消息时,发现线程池有空间时可以提交到线程池,让线程池的工作线程去处理,它继续保持拉取速度。
这样既控制了线程池占用的内存,又可以让消息处理线程池处理不过来时多一个线程处理消息。
由于上面的代码采用调用者执行的方式,那么要考虑消息处理的顺序问题,比如一个订单的处理可能有多个步骤,对应多条 MQ 消息,那么要考虑这些步骤如果乱序了是否可以接受,因为第3步骤的处理消息可能被 MQListener 处理了,而第2步的处理消息还积压在线程池里。
相关 [线程 mq 消息] 推荐:
- 码蜂笔记
业务部门反应网站访问特别慢,负责运维监控的同事说MQ消息队列积压了,中间件的说应用服务器内存占用很高,GC 一直回收不了内存,GC 线程占了近 100% 的 CPU,其他的基本上都在等待,数据库很正常,完全没压力. 没啥办法,线程、堆 dump 出来后,重启吧,然后应用又正常了. 这种故障之前其实也碰到过了,分析了当时 dump 出来的堆后发现,处理 MQ 消息的线程池的队列长度达百万级别,占用了超过 1.3G
内存,这些内存都是没法回收的.
- 开源软件 - ITeye博客
本文主要讲解关于kafka mq的设计思想及个人理解. 关于kafka的详细信息,大家可以参考官网的文献
http://kafka.apache.org/documentation.html这是一篇相当不错的文章,值得仔细研读. 第一个问题:消息队列(Message Queue)是干嘛用的. 首先,要对消息队列有一个基本的理解.
- 企业架构 - ITeye博客
目前常用的消息队列组建无非就是MSMQ和ActiveMQ,至于他们的异同,这里不想做过多的比较. 简单来说,MSMQ内置于微软操作系统之中,在部署上包含一个隐性条件:Server需要是微软操作系统. (对于这点我并去调研过MSMQ是否可以部署在非微软系统,比如:Linux,只是拍脑袋想了想,感觉上是不可以).
- 编程语言 - ITeye博客
本文实例是基于
WebSphere MQ中将消息发送至远程队列的配置的基础上的,且如果要能正常运行并发送、接收消息,还需要在两个队列管理器(QM_ORANGE和QM_APPLE)上做如下配置或修改.
1.创建名称为DC.SVRCONN的服务器连接通道.
2.将队列管理器的通道认证记录设置为“已禁用”.
- 行业应用 - ITeye博客
假设在IBM MQ中定义的队列管理器的名为QueueManager, 端口1414,CCSID 437 ,创建名为LQ1,LQ2的队列分别用于发送和接收消息, 服务器连接通道名为SVRCONN. 确保在项目的Classpath中导入了以下的jar包:. 如果需使用spring的JmsTemplate方式来读写MQ,还需要导入.
- 编程语言 - ITeye博客
2、定义队列缓冲池最大消息数,如果达到该值,那么队列检入将等待检出低于该值时继续进行. 3、定义检出线程,如果队列缓冲池没有消息,那么检出线程会线程等待中. if(size==0){
//队列缓存池没有消息,等待. if(isIpLock(queueStr)){//假若这个是一个多应用的分布式系统,那么这个判断应该是分布式锁,这里说的锁不是线程停止,而是跳过该消息,滞后处理.
- 藏书人 - 李志官方博客
1,经过深思熟虑,我放弃了十月份做个人小巡演的计划,全心全意投入跨年音乐会的准备工作. 如不出意外,12月31日南京见. 2,如果不出意外,第六张专辑会在十一之前发布. 经过深思熟虑,我决定不做实体,直接放到官网提供下载,能者多劳,愿者给钱. 3,当然对我而言,意外是常态.
- 水御龙神 - 1416 教室
每一个光鲜的封面,都饱含美术编辑的”血泪“和杂志主编的“阴谋”——今天的消息树让我们将掀开封面往里瞅瞅. 最新一期的美国新闻周刊封面,实在让人有点儿难以置信. 优雅的戴安娜王妃突然出现在二十一世纪的街头,旁边是她的儿媳妇Kate,但仔细看,她却不是当年的王妃,变老了,变丑了——这是新闻周刊编辑们想象中的一个五十岁的女人的样子.
坚持分享优质有趣的原创文章,并保留作者信息和版权声明,任何问题请联系:@。捕获Java线程池执行任务抛出的异常 - 为程序员服务
捕获Java线程池执行任务抛出的异常
Java中线程执行的任务接口java.lang.Runnable 要求不抛出Checked异常, public interface Runnable {
public abstract void run();
} 那么如果 run() 方法中抛出了RuntimeException,将会怎么处理了? 通常java.lang.Thread对象运行设置一个默认的异常处理方法: java.lang.Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)
而这个默认的静态全局的异常捕获方法时输出堆栈。 当然,我们可以覆盖此默认实现,只需要一个自定义的java.lang.Thread.UncaughtExceptionHandler接口实现即可。 public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
} 而在线程池中却比较特殊。默认情况下,线程池 java.util.concurrent.ThreadPoolExecutor 会Catch住所有异常, 当任务执行完成(java.util.concurrent.ExecutorService.submit(Callable))获取其结果 时(java.util.concurrent.Future.get())会抛出此RuntimeException。 /**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an exception
* @throws InterruptedException if the current thread was interrupted while waiting
V get() throws InterruptedException, ExecutionE 其中 ExecutionException 异常即是java.lang.Runnable 或者 java.util.concurrent.Callable 抛出的异常。 也就是说,线程池在执行任务时捕获了所有异常,并将此异常加入结果中。这样一来线程池中的所有线程都将无法捕获到抛出的异常。 从而无法通过设置线程的默认捕获方法拦截的错误异常。 也不同通过
来完成异常的拦截。 好在java.util.concurrent.ThreadPoolExecutor 预留了一个方法,运行在任务执行完毕进行扩展(当然也预留一个protected方法beforeExecute(Thread t, Runnable r)): protected void afterExecute(Runnable r, Throwable t) { }
此方法的默认实现为空,这样我们就可以通过继承或者覆盖ThreadPoolExecutor 来达到自定义的错误处理。 解决办法如下: ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(11, 100, 1, TimeUnit.MINUTES, //
new ArrayBlockingQueue&Runnable&(10000),//
new DefaultThreadFactory()) {
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
printException(r, t);
private static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future&?&) {
Future&?& future = (Future&?&)
if (future.isDone())
future.get();
} catch (CancellationException ce) {
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
if (t != null)
log.error(t.getMessage(), t);
} 此办法的关键在于,事实上 afterExecute 并不会总是抛出异常 Throwable t,通过查看源码得知,异常是封装在此时的Future对象中的, 而此Future对象其实是一个java.util.concurrent.FutureTask的实现,默认的run方法其实调用的 java.util.concurrent.FutureTask.Sync.innerRun()。 void innerRun() {
if (!compareAndSetState(0, RUNNING))
runner = Thread.currentThread();
if (getState() == RUNNING) // recheck after setting thread
innerSet(callable.call());
releaseShared(0); // cancel
} catch (Throwable ex) {
innerSetException(ex);
void innerSetException(Throwable t) {
for (;;) {
int s = getState();
if (s == RAN)
if (s == CANCELLED) {
// aggressively release to set runner to null,
// in case we are racing with a cancel request
// that will try to interrupt runner
releaseShared(0);
if (compareAndSetState(s, RAN)) {
exception =
releaseShared(0);
} 这里我们可以看到它吃掉了异常,将异常存储在java.util.concurrent.FutureTask.Sync的exception字段中: /** The exception to throw from get() */
当我们获取异步执行的结果时, java.util.concurrent.FutureTask.get() public V get() throws InterruptedException, ExecutionException {
return sync.innerGet();
} java.util.concurrent.FutureTask.Sync.innerGet() V innerGet() throws InterruptedException, ExecutionException {
acquireSharedInterruptibly(0);
if (getState() == CANCELLED)
throw new CancellationException();
if (exception != null)
throw new ExecutionException(exception);
} 异常就会被包装成ExecutionException异常抛出。 也就是说当我们想线程池 ThreadPoolExecutor(java.util.concurrent.ExecutorService)提交任务时, 如果不理会任务结果(Feture.get()),那么此异常将被线程池吃掉。 &T& Future&T& submit(Callable&T& task);
Future&?& submit(Runnable task); 而java.util.concurrent.ScheduledThreadPoolExecutor是继承ThreadPoolExecutor的,因此情况类似。 结论,通过覆盖ThreadPoolExecutor.afterExecute 方法,我们才能捕获到任务的异常(RuntimeException)。
原文地址:
IT社区推荐资讯 - ITIndex.net
原文地址:, 感谢原作者分享。
您可能感兴趣的代码为什么在运行多线程程序时,内存的使用率突然暴涨?_java吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:538,659贴子:
为什么在运行多线程程序时,内存的使用率突然暴涨?收藏
java上海北大青鸟于达校区专注java,免试入学,专科保底,学历+技能,包就业!喜迎新春,更有一大拨免费试听课程来袭,点击预约试听名额~北大青鸟于达校区
来个大神,给点建议~~
查看是否有死循环之类的代码。
是因为你在使用三六蛋
注意线程的休眠,,非常重要,,对线程无非就是划分cpu时间片段,,但是速度非常之快,,哪怕你每一个线程休眠一毫秒,,cpu的占用都是天壤之别,
交替休眠 要注意,真的就是1毫秒就会有很大差距
sleep(1)效果立马就不一样了
露珠这配置也太差了4个线程就这样了
内存暴涨,这样看能懂的是大神
上海java培训,选达内,美国上市教育机构,「java培训之父」Sun认证,先就业后付款!达内java培训,名师授课,0基础120天速成java工程师,0元试学!学习+认证+就业=薪前景!
sleep一下。。。sleep和不sleep差别太大了
图片来自:内存算了球,我8框框i7 4710CPU都满载,我这个是因为出现死循环,没有break跳出
别逗,jvm有限制大小,如果超出jvm大小,会报堆栈溢出错误,所以你打开资源监视器,点到内存上,看看那个程序在消耗内存
登录百度帐号推荐应用
为兴趣而生,贴吧更懂你。或线程池调整真的很重要 - ImportNew
知道吗,你的Java web应用其实是使用线程池来处理请求的。这一实现细节被许多人忽略,但是你迟早都需要理解线程池如何使用,以及如何正确地根据应用调整线程池配置。这篇文章的目的是为了解释线程模型——什么是线程池、以及怎样正确地配置线程池。
单线程模型
让我们从一些基础的线程模型开始,然后再随着线程模型的演变进行更深一步的学习。你使用的任何应用服务器或框架,如、、等,它们的基本原理其实是相同的。Web服务器的最底层实际上是一个socket。这个socket监听并接受到达的TCP连接。一旦一个连接被建立,就可以通过这个新建立的连接读取、解析信息,然后将这些信息包装成一个HTTP请求。这个HTTP请求还将被移交至web应用程序,来完成请求的动作。
我们将通过一个简单的服务器程序来展示线程在其中所起到的作用。这个服务器程序展示了大部分应用服务器的底层实现细节。让我们以一个简单的单线程web服务器程序开始,它的代码像下面这样:
ServerSocket listener = new ServerSocket(8080);
while (true) {
Socket socket = listener.accept();
handleRequest(socket);
} catch (IOException e) {
e.printStackTrace();
} finally {
listener.close();
这段代码在8080端口上创建了一个,紧接着通过循环来监听和接受新到达的连接。一旦连接建立,会将socket传递给handleRequest方法。这个方法可能会读取该HTTP请求,处理这个请求,然后写回一个响应。在这个简单的例子中,handleRequest方法从socket中读取简单的一行数据,然后返回一个简短的HTTP响应。但是,handleRequest有可能需要处理一些更复杂的任务,例如读数据库或者执行其它一些IO操作。
final static String response =
&HTTP/1.0 200 OKrn& +
&Content-type: text/plainrn& +
&Hello Worldrn&;
public static void handleRequest(Socket socket) throws IOException {
// Read the input stream, and return &200 OK&
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
(in.readLine());
OutputStream out = socket.getOutputStream();
out.write(response.getBytes(StandardCharsets.UTF_8));
} finally {
socket.close();
因为只有一个线程处理所有的socket,因此只有在完全处理好一个请求后,才能再接受下一个请求。在实际的应用中,handleRequest方法可能需要经过100毫秒才能返回,那么这个服务器程序在一秒中,只能按顺序处理10个请求。
多线程模型
尽管handleRequest可能会被IO操作阻塞,CPU却可能是空闲的,它可以处理其它更多请求,但这对单线程模型来说是不能实现的。因此,通过创建多个线程,可以使服务器程序实现并发操作:
public static class HandleRequestRunnable implements Runnable {
public HandleRequestRunnable(Socket socket) {
this.socket =
public void run() {
handleRequest(socket);
} catch (IOException e) {
e.printStackTrace();
// Main loop here
ServerSocket listener = new ServerSocket(8080);
while (true) {
Socket socket = listener.accept();
new Thread( new HandleRequestRunnable(socket) ).start();
} finally {
listener.close();
上面这段代码中,accept()方法仍然是在一个单线程循环中被调用。但是当TCP连接建立,socket创建时,服务器就创建一个新的线程。这个新生的线程将执行和单线程模型中一样的handleRequest方法。
新线程的建立使调用accept方法的线程能够处理更多的TCP连接,这样服务器就能并发地处理请求了。这一技术被称为“thread per request”(一个线程处理一个请求),也是现在最流行的服务器技术。值得注意的是,还有一些其它的服务器技术,如和采用的事件驱动异步模型,它们都没有使用线程池。因此,它们都不在本文的讨论范围内。
“thread per request”方式里创建新线程(稍后销毁这个线程)的操作是昂贵的,因为Java虚拟机和操作系统都需要为这一操作分配资源。另外,在上面那段代码的中,可以创建的线程数量是不受限制的。这么做的隐患很大,因为它可能导致服务器资源迅速枯竭。
每个线程都需要一定的内存空间来作为自己的栈空间。在最近的64位虚拟机版本中,。如果server收到很多请求,或者handleRequest方法的执行时间变得比较长,就会造成服务器产生很多并发线程。如果要维护1000个线程,仅就栈空间而言,虚拟机就必须耗费1GB的RAM空间。另外,为处理请求,每个线程都会在堆上产生许多对象,这就有可能导致虚拟机的堆空间被迅速占满,给虚拟机的垃圾收集器带来很大压力,造成频繁的垃圾回收,最终导致。
线程消耗的不仅是RAM资源,这些线程还可能消耗其它有限的资源,例如文件句柄、数据库连接等。过多地消耗这类资源可能导致一些其它的错误或造成系统崩溃。因此,要防止系统资源被线程耗尽,就必须对服务器产生的线程数量做出限制。
通过使用-Xss参数来调整每个线程的栈空间,可以在一定程度上解决资源枯竭的问题,但它绝不是灵丹妙药。一个小的栈空间可以使得每个线程占用的内存减小,但这样可能会造成栈溢出错误。栈空间的调整方式不尽相同,但是对许多应用来说,1024KB过于浪费了,而256KB或512KB会更加合适。Java所允许的最小栈空间的大小是160KB。
可以通过一个简单的线程池来避免持续地创建新线程,限制最大线程数量。线程池跟踪着所有线程,在线程数量达到上限前,它会创建新的线程,当有空闲线程时,它会使用空闲线程。
ServerSocket listener = new ServerSocket(8080);
ExecutorService executor = Executors.newFixedThreadPool(4);
while (true) {
Socket socket = listener.accept();
executor.submit( new HandleRequestRunnable(socket) );
} finally {
listener.close();
上面这段代码使用了ExecutorService类来提交任务(Runnable)。提交的任务将会被线程池中的线程执行,而不是通过新创建的线程执行。在这个例子中,所有的请求都通过一个线程数量固定为4的线程池来完成。这个线程池限制了并发执行的请求数量,从而限制了系统资源的使用。
除了方法创建的线程池外,Executors类还提供了 方法来创建线程池。这种线程池同样有无法限制线程数量的问题,但是它会优先使用线程池中已创建的空闲线程来处理请求。这种类型的线程池特别适用于执行短期任务的请求,因为它们不会长时间的阻塞外部资源。
类也可以直接创建,这样就可以对它进行一些个性化的配置。例如可以配置线程池内最小线程数和最大线程数,也可以配置线程创建和销毁的策略。稍后,本文将介绍这样的例子。
对于线程数量固定的线程池,善于观察的读者可能会提出这样的一个疑问:当线程池中的线程都在工作时,一个新的请求到达,会发生什么呢?当线程池中的线程都在工作时,ThreadPoolExecutor可能会使用一个队列来组织新到达的请求,直到线程池中有空闲的线程可以使用。Executors.nexFixedThreadPool方法会默认创建一个没有长度限制的LinkedList。这个LinkedList也可能会产生系统资源耗尽的问题,虽然这个过程会比较缓慢,因为队列中的请求所占用的资源比线程占用的资源要少得多。但是在我们的例子中,队列中的每个请求都保持着一个socket,而每一个socket都需要打开一个文件句柄,操作系统对同时打开的文件句柄数量是有限制的,所以队列中保持socket并不是一个好的方式,除非必须这么做。因此,限制工作队列的长度也是有意义的。
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);
while (true) {
Socket socket = listener.accept();
executor.submit( new HandleRequestRunnable(socket) );
} finally {
listener.close();
我们再一次创建一个线程池,这一次我们没有使用Executors.newFixedThreadPool方法,而是自定义了一个ThreadPoolExecutor,在构造方法中传递了一个大小限制为16个元素的。同样的,类ArrayBlockingQueue也可以被用来限制队列的长度。
如果所有的线程都在执行任务,而且工作队列也被请求填满了,此时对于新到达请求的处理方式,取决于ThreadPoolExecutor构造方法的最后一个参数。在我们这个例子中,我们使用的是,这个参数会让线程池丢弃新到达的请求。还有一些其它的处理策略,例如会让Executor抛出一个异常,会使任务在它的调用端线程池中执行。CallerRunsPolicy策略提供了一个简单的方式来限制任务提交的速度。但是这样做可能是有害的,因为它会阻塞一个原本不应被阻塞的线程。
一个好的默认策略应该是Discard或Abort,它们都会使线程池丢弃新到达的任务。这样服务器就能容易地向客户端响应一个错误,例如。有的人可能会认为,队列的长度应该是允许增长的,这样所有的任务最终都能被执行。但是用户是不愿意长时间等待的,而且若任务到达的速度超过任务处理的速度,队列将会无限地增长。队列是被用来缓冲突然爆发的请求,或者处理短期任务的,通常情况下,队列应该是空的。
多少线程合适呢?
现在,我们知道了如何创建一个线程池。但是有一个更困难的问题,线程池里应该创建多少个线程呢?我们已经知道了线程池中的最大线程数量应该被限制,才不会导致系统资源耗尽。这些系统资源包括了内存(堆栈)、打开的文件句柄、打开的TCP连接、打开的数据库连接以及其它有限的系统资源。相反的,如果线程执行的是CPU密集型任务而不是IO密集型任务,服务器的物理内核数就应该被视为是有限的资源,这样创建的线程数就不应该超过系统的内核数。
系统应创建多少线程取决于这个应用执行的任务。开发人员应使用现实的请求来对系统进行负载测试,测试不同的线程池大小配置对系统的影响。每次测试都增加线程池的大小,直到系统达到崩溃的临界点。这个方法使你可以发现线程池线程数量的上限。超过这个上限,系统的资源将耗尽。在某些情况下,可以谨慎地增加系统的资源,例如分配更多的RAM空间给JVM,或者调整操作系统使其支持同时打开更多的文件句柄。然而,在某些情况下创建的线程数量会达到我们测试出的理论上限,这非常值得我们注意。稍后还会看到这方面的内容。
利特尔法则
排队论,特别的,,可以用来帮助我们理解线程池的一些特性。简单地说,利特尔法则解释了这三种变量的关系:L—系统里的请求数量、λ—请求到达的速率和W—每个请求的处理时间。例如,如果每秒10个请求到达,处理一个请求需要1秒,那么系统在每个时刻都有10个请求在处理。如果处理每个请求的时间翻倍,那么系统每时刻需要处理的请求数也翻倍为20,因此需要20个线程。
任务的执行时间对于系统中正在处理的请求数量有着很大的影响,一些后端资源的迟延,例如数据库,通常会使得请求的处理时间被延长,从而导致线程池中的线程被迅速用尽。因此,理论上测出的线程数上限对于这种情况就不是很合适,这个上限值还应该考虑到线程的执行时间,并结合理论上的上限值。
例如,假设JVM最多能同时处理的请求数为1000。如果我们预计每个请求需要耗费的时间不超过30秒,那么,在最坏的情况下我们每秒能同时处理的请求数不会超过33
1/3 个。但是,如果一切都很顺利,每个请求只需使用500ms就可以完成,那么通过1000个线程应用每秒就可以处理2000个请求。当系统突然出现短暂的任务执行迟延的问题时,通过使用一个队列来减缓这一问题是可行的。
为什么线程数配置不当会带来麻烦?
如果线程池的线程数量过少,我们就无法充分利用系统资源,这使得用户需要花费很长时间来等待请求的响应。但是,如果允许创建过多的线程,系统的资源又会被耗尽,这会对系统造成更大的破坏。
不仅仅是本地的资源被耗尽,其它一些应用也会受到影响。例如,许多应用都使用同一个后端数据库进行查询等操作。数据库有并发连接数量的限制。如果一个应用不加限制地占用了所有数据库连接,其它获取数据库连接的应用都将被阻塞。这将导致许多应用运行中断。
更糟的是,资源耗尽还会引发一些连锁故障。设想这样一个场景,一个应用有许多个实例,这些实例都运行在一个负载均衡器之后。如果一个实例因为过多的请求而占用了过多内存,JVM就需要花更多的时间进行垃圾收集工作,那么JVM处理请求的时间就减少了。这样一来,这个应用实例处理请求的能力降低了,系统中的其它实例就必须处理更多的请求。其它的实例也会因为请求数过多以及线程池大小没有限制的原因产生资源枯竭等问题。这些实例用尽了内存资源,导致虚拟机进行频繁地内存收集操作。这样的恶性循环会在这些实例中产生,直到整个系统奔溃。
我见过许多没有进行负载测试的应用,这些应用能够创建任意多的线程。通常情况下,这些应用只要很少数量的线程就能处理好以一定速率到达的请求。但是,如果应用需要使用其它的一些远程服务来处理用户请求,而这个远程服务的处理能力突然降低了,这将增加【大】W的值(应用处理请求的平均时间)。这样,线程池的线程就会被迅速用尽。如果对应用进行线程数量的负载测试,那么资源枯竭问题就会在测试中显现出来。
多少个线程池合适?
对于和(SOA)来说,它们通常需要请求一些后端服务。线程池的配置非常容易导致程序失败,因此必须谨慎地配置线程池。如果远程服务的性能下降,系统中的线程数量就会迅速达到线程池的上限,其它后续到达的服务就会被丢弃。这些后续的请求也许并不是要使用性能出现故障的服务,但是它们都只能被丢弃了。
针对不同的后端服务请求,设置不同的线程池可以解决这一问题。在这个模式中,仍然使用同一个线程池来处理用户的请求,但是当用户的请求需要调用一个远程服务时,这个任务就被传递给一个指定的后端线程池。这样处理用户请求的主线程池就不会因为调用后端服务而产生很大的负担。当后端服务出现故障时,只有调用这个服务的线程池才会受到影响。
使用多个线程池还有一个好处,就是它能帮助避免出现死锁问题。如果每个空闲线程都因为一个尚未处理完毕的请求阻塞,就会发生死锁,没有一个线程可以继续往下执行。如果使用多个线程池,理解好每个线程池应负责的工作,那么死锁的问题就能在一定程度上避免。
截止时间和一些最佳实践
一个最佳实践是给需要远程调用的请求规定一个截止时间。如果远程服务在规定的时间内没有响应,就丢弃这个请求。这样的技术也可以用在线程池中,如果线程处理某个请求的时间超过了规定时间,那么这个线程就应被停止,为新到达的请求腾出资源,这样也就给W(处理请求的平均时间)规定了上限。虽然这样的做法看起来有些浪费,但是如果一个用户(特别是当用户在使用浏览器时),在等待请求的响应,那么30秒以后,浏览器无论如何也会放弃这个请求,或者更有可能的是:用户不会耐心地等待这个请求响应,而是进行其它操作去了。
快速失败也是一个可以用来处理后端请求的线程池方案。如果后端服务失效了,线程池中的线程数会迅速到达上限,这些线程都在等待没有响应的后端服务。如果使用快速失败机制,当后端服务被标记为失效时,所有的后续请求都会迅速失败,而不是进行不必要的等待。当然,它也需要一种机制来判断后端何时恢复为可用的。
最后,如果一个请求需要独立地调用多个后端服务,那么这个请求就应能并行地调用这些后端服务,而不是顺序地进行。这样就能降低请求的等待时间,但这是以增加线程数为代价的。
幸运的是,有一个非常好的库hystrix,这个库封装了许多很好的线程策略,然后以非常简单和友好的方式将这些接口暴露出来。
我希望这篇文章能改进你对线程池的理解。一个合适的线程池配置需要理解应用的需求,还需要考虑这几个因素,系统允许的最大线程数、处理用户请求所需的时间。好的线程池配置不仅可以避免系统出现连锁故障,还能帮助计划和提供服务。
即使你的应用没有直接地使用一个线程池,它们也间接地通过应用服务器或其它更高级的抽象形式使用了线程池。、、、 都提供了多种可配置的线程池(这些线程池正是你编写的Servlet运行的地方)。
原文链接:
- 译文链接: [ 转载请保留原文出处、译者和译文链接。]
关于作者:
(新浪微博:)
谢谢指正 已更正
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2016 ImportNew}

我要回帖

更多关于 java线程池内存泄漏 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信