新的起点 新的开始

快乐生活 !

深入浅出多线程(4)对CachedThreadPool OutOfMemoryError问题的一些想法

    接系列3,在该系列中我们一起探讨一下CachedThreadPool。
    线程池是Conncurrent包提供给我们的一个重要的礼物。使得我们没有必要维护自个实现的心里很没底的线程池了。但如何充分利用好这些线程池来加快我们开发与测试效率呢?当然是知己知彼。本系列就说说对CachedThreadPool使用的一下问题。
    下面是对CachedThreadPool的一个测试,程序有问题吗?
package net.blogjava.vincent;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CachedThreadPoolIssue {

    
/**
     * 
@param args
     
*/
    
public static void main(String[] args) {
        
        ExecutorService es 
= Executors.newCachedThreadPool();
        
for(int i = 1; i<8000; i++)
            es.submit(
new task());

    }

}
class task implements Runnable{

    @Override
    
public void run() {
    
try {
        Thread.sleep(
4000);
    } 
catch (InterruptedException e) {
        
// TODO Auto-generated catch block
        e.printStackTrace();
    }
        
    }
    
}
如果对JVM没有特殊的设置,并在Window平台上,那么就会有一下异常的发生:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.addIfUnderMaximumPoolSize(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)
    at java.util.concurrent.AbstractExecutorService.submit(Unknown Source)
    at net.blogjava.vincent.CachedThreadPoolIssue.main(CachedThreadPoolIssue.java:19)
看看Doc对该线程池的介绍:
Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks. Calls to execute will reuse previously constructed threads if available. If no existing thread is available, a new thread will be created and added to the pool. Threads that have not been used for sixty seconds are terminated and removed from the cache. Thus, a pool that remains idle for long enough will not consume any resources. Note that pools with similar properties but different details (for example, timeout parameters) may be created using ThreadPoolExecutor constructors.

有以下几点需要注意:
1. 指出会重用先前的线程,不错。
2. 提高了短Task的吞吐量。
3. 线程如果60s没有使用就会移除出Cache。

好像跟刚才的错误没有关系,其实就第一句话说了问题,它会按需要创建新的线程,上面的例子一下提交8000个Task,意味着该线程池就会创建8000线程,当然,这远远高于JVM限制了。
注:在JDK1.5中,默认每个线程使用1M内存,8000M !!! 可能吗!!

所以我感觉这应该是我遇到的第一个Concurrent不足之处,既然这么设计,那么就应该在中Doc指出,应该在使用避免大量Task提交到给CachedThreadPool.
可能读者不相信,那么下面的例子说明了他创建的Thread。
在ThreadPoolExecutor提供的API中,看到它提供beforeExecute 和afterExecute两个可以在子类中重载的方法,该方法在线程池中线程执行Task之前与之后调用。所以我们在beforeExexute中查看目前线程编号就可以确定目前的线程数目.
package net.blogjava.vincent;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class CachedThreadPoolIssue {

    
/**
     * 
@param args
     
*/
    
public static void main(String[] args) {
        
        ExecutorService es 
= new LogThreadPoolExecutor(0, Integer.MAX_VALUE,
                
60L, TimeUnit.SECONDS,
                
new SynchronousQueue<Runnable>());
        
for(int i = 1; i<8000; i++)
            es.submit(
new task());

    }

}
class task implements Runnable{

    @Override
    
public void run() {
    
try {
        Thread.sleep(
600000);
    } 
catch (InterruptedException e) {
        
// TODO Auto-generated catch block
        e.printStackTrace();
    }
        
    }
    
}
class LogThreadPoolExecutor extends ThreadPoolExecutor{

    
public LogThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
            
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }
    
protected void beforeExecute(Thread t, Runnable r) { 
        System.out.println(t.getName());
    }
    
protected void afterExecute(Runnable r, Throwable t) {
    }
    
}
测试结果如图:

当线程数达到5592是,只有在任务管理器Kill该进程了。

如何解决该问题呢,其实在刚才实例化时就看出来了,只需将
new LogThreadPoolExecutor(0, Integer.MAX_VALUE,
                60L, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>());
Integer.MAX_VALUE 改为合适的大小。对于该参数的含义,涉及到线程池的实现,将会在下个系列中指出。
当然,其他的解决方案就是控制Task的提交速率,避免超过其最大限制。

posted on 2008-09-03 21:50 advincenting 阅读(7895) 评论(2)  编辑  收藏

评论

# re: 深入浅出多线程(4)对CachedThreadPool OutOfMemoryError问题的一些想法 2008-09-04 08:59 dennis

实际应用中我想用到CachedThreadPool的机会不多,通常情况下都推荐使用FixedThreadPool以规定线程数上限,也利于调优  回复  更多评论   

# re: 深入浅出多线程(4)对CachedThreadPool OutOfMemoryError问题的一些想法 2008-09-29 11:38 niuren

同意, CachedThreadPool内部使用的是SynchronousQueue, 感觉这个结构本身就决定了CachedThreadPool很慢。所以一般情况下还是用FixedThreadPool或自定义的ThreadPoolExecut  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

公告

Locations of visitors to this page

导航

<2008年9月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

统计

常用链接

留言簿(13)

随笔分类(71)

随笔档案(179)

文章档案(13)

新闻分类

IT人的英语学习网站

JAVA站点

优秀个人博客链接

官网学习站点

生活工作站点

最新随笔

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜