java线程那些事_Java线程池的那些事

对线程池的误解

很长一段时间里我一直以为java线程池是为了提高多线程下创建线程的效率。创建好一些线程并缓存在线程池里,后面来了请求(Runnable)就 从连接池中取出一个线程处理请求;这样就避免了每次创建一个新Thread对象。直到前段时间我看到一篇Neal Gafter(和Joshua Bloch合著了《Java Puzzlers》,现任职于微软,主要从事.NET语言方面的工作)的访谈,里面有这么一段谈话(http://www.infoq.com/cn/articles/neal-gafter-on-java):

4b1b9864ecae4fe76c09057c8021e2ab.png

乍一看,大神的思路就是不一样:java线程池是为了防止java线程占用太多资源?

虽然是java大神的访谈,但是也不能什么都信,你说占资源就占资源?还是得写测试用例测一下。

首先验证下我的理解:

java线程池和创建java线程哪个效率高?

直接上测试用例:

publicclassThreadPoolTestextendsTestCase {

privatestaticfinalintCOUNT =10000;

publicvoidtestThreadPool()throwsInterruptedException {

CountDownLatch countDownLatch = newCountDownLatch(COUNT);

ExecutorService executorService = Executors.newFixedThreadPool(100);

longbg = System.currentTimeMillis();

for(inti =0; i 

Runnable command = newTestRunnable(countDownLatch);

executorService.execute(command);

}

countDownLatch.await();

System.out.println("testThreadPool:"+ (System.currentTimeMillis() - bg));

}

publicvoidtestNewThread()throwsInterruptedException {

CountDownLatch countDownLatch = newCountDownLatch(COUNT);

longbg = System.currentTimeMillis();

for(inti =0; i 

Runnable command = newTestRunnable(countDownLatch);

Thread thread = newThread(command);

thread.start();

}

countDownLatch.await();

System.out.println("testNewThread:"+ (System.currentTimeMillis() - bg));

}

privatestaticclassTestRunnableimplementsRunnable {

privatefinalCountDownLatch countDownLatch;

TestRunnable(CountDownLatch countDownLatch) {

this.countDownLatch = countDownLatch;

}

@Override

publicvoidrun() {

countDownLatch.countDown();

}

}

}

这里使用Executors.newFixedThreadPool(100)是为了控制线程池的核心连接数和***连接数一样大,都为100。

我的机子上的测试结果:

testThreadPool:31

testNewThread:624

可以看到,使用线程池处理10000个请求的处理时间为31ms,而每次启用新线程的处理时间为624ms。

好了,使用线程池确实要比每次都创建新线程要快一些;但是testNewThread一共耗时624ms,算下平均每次请求的耗时为:

624ms/10000=62.4us

每次创建并启动线程的时间为62.4微秒。根据80/20原理,这点儿时间根本可以忽略不计。所以线程池并不是为了效率设计的。

java线程池是为了节约资源?

再上测试用例:

publicclassThreadPoolTestextendsTestCase {

publicvoidtestThread()throwsInterruptedException {

inti =1;

while(true) {

Runnable command = newTestRunnable();

Thread thread = newThread(command);

thread.start();

System.out.println(i++);

}

}

privatestaticclassTestRunnableimplementsRunnable {

@Override

publicvoidrun() {

try{

Thread.sleep(1000);

} catch(InterruptedException e) {

e.printStackTrace();

}

}

}

}

以上用例模拟每次请求都创建一个新线程处理请求,然后默认每个请求的处理时间为1000ms。而在我的机子上当请求数达到1096时会内存溢出:

java.lang.OutOfMemoryError: unable to create new native thread

为什么会抛OOM Error呢?因为jvm会为每个线程分配一定内存(JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K,也可以通过jvm参数-Xss来设置),所以当线程数达到一定数量时就报了该error。

设想如果不使用java线程池,而为每个请求都创建一个新线程来处理该请求,当请求量达到一定数量时一定会内存溢出的;而我们使用java线程池的话,线程数量一定会<=maximumPoolSize(线程池的***线程数),所以设置合理的话就不会造成内存溢出。

现在问题明朗了:java线程池是为了防止内存溢出,而不是为了加快效率。

浅谈java线程池

上文介绍了java线程池启动太多会造成OOM,使用java线程池也应该设置合理的线程数数量;否则应用可能十分不稳定。然而该如何设置这个数量呢?我们可以通过这个公式来计算:

(MaxProcessMemory – JVMMemory – ReservedOsMemory) / (ThreadStackSize) = Max number of threads

MaxProcessMemory     进程***的内存

JVMMemory                 JVM内存

ReservedOsMemory     JVM的本地内存

ThreadStackSize            线程栈的大小

MaxProcessMemory

MaxProcessMemory:进程***的寻址空间,当然也不能超过虚拟内存和物理内存的总和。关于不同系统的进程可寻址的***空间,可参考下面表格:

Maximum Address Space Per Process

Operating System

Maximum Address Space Per Process

Redhat Linux 32 bit

2 GB

Redhat Linux 64 bit

3 GB

Windows 98/2000/NT/Me/XP

2 GB

Solaris x86 (32 bit)

4 GB

Solaris 32 bit

4 GB

Solaris 64 bit

Terabytes

JVMMemory

JVMMemory: Heap + PermGen,即堆内存和***代内存和(注意,不包括本地内存)。

ReservedOsMemory

ReservedOSMemory:Native heap,即JNI调用方法所占用的内存。

ThreadStackSize

ThreadStackSize:线程栈的大小,JDK5.0以后每个线程堆栈大小默认为1M,以前每个线程堆栈大小为256K;可以通过jvm参数-Xss来设置;注意-Xss是jvm的非标准参数,不强制所有平台的jvm都支持。

如何调大线程数?

如果程序需要大量的线程,现有的设置不能达到要求,那么可以通过修改MaxProcessMemory,JVMMemory,ThreadStackSize这三个因素,来增加能创建的线程数:

MaxProcessMemory 使用64位操作系统

JVMMemory   减少JVMMemory的分配

ThreadStackSize  减小单个线程的栈大小

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值