熟悉java多线程的朋友一定十分了解java的线程池,jdk中的核心实现类为java.util.concurrent.ThreadPoolExecutor。大家可能了解到它的原理,甚至看过它的源码;但是就像我一样,大家可能对它的作用存在误解。现在问题来了,jdk为什么要提供java线程池?使用java线程池对于每次都创建一个新Thread有什么优势?

对线程池的误解

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

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

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

首先验证下我的理解:

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

直接上测试用例:

public class ThreadPoolTest extends TestCase {
private static final int COUNT = 10000; public void testThreadPool() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(COUNT);
ExecutorService executorService = Executors.newFixedThreadPool(100);
long bg = System.currentTimeMillis();
for (int i = 0; i < COUNT; i++) {
Runnable command = new TestRunnable(countDownLatch);
executorService.execute(command);
}
countDownLatch.await();
System.out.println("testThreadPool:" + (System.currentTimeMillis() - bg));
} public void testNewThread() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(COUNT);
long bg = System.currentTimeMillis();
for (int i = 0; i < COUNT; i++) {
Runnable command = new TestRunnable(countDownLatch);
Thread thread = new Thread(command);
thread.start();
}
countDownLatch.await();
System.out.println("testNewThread:" + (System.currentTimeMillis() - bg));
} private static class TestRunnable implements Runnable {
private final CountDownLatch countDownLatch; TestRunnable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
} @Override
public void run() {
countDownLatch.countDown();
}
}
}

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

我的机子上的测试结果:

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

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

624ms/10000=62.4us

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

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

再上测试用例:

public class ThreadPoolTest extends TestCase {
public void testThread() throws InterruptedException {
int i = 1;
while (true) {
Runnable command = new TestRunnable();
Thread thread = new Thread(command);
thread.start();
System.out.println(i++);
}
} private static class TestRunnable implements Runnable {
@Override
public void run() {
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 减小单个线程的栈大小
 

Java线程池的那些事的更多相关文章

  1. java线程池的初探

    问题来源 发现学习很多技术都提到了线程池的技术,自己的线程池方面没有仔细研究过,现在看了点东西来这里总结下,最近发现写博客是一个很好的锻炼自己并且将学到的东西更加理解的一个方式. 问题探究 java的 ...

  2. 【转载】深度解读 java 线程池设计思想及源码实现

    总览 开篇来一些废话.下图是 java 线程池几个相关类的继承结构: 先简单说说这个继承结构,Executor 位于最顶层,也是最简单的,就一个 execute(Runnable runnable) ...

  3. Java并发指南12:深度解读 java 线程池设计思想及源码实现

    ​深度解读 java 线程池设计思想及源码实现 转自 https://javadoop.com/2017/09/05/java-thread-pool/hmsr=toutiao.io&utm_ ...

  4. Java线程池进阶

    线程池是日常开发中常用的技术,使用也非常简单,不过想使用好线程池也不是件容易的事,开发者需要不断探索底层的实现原理,才能在不同的场景中选择合适的策略,最大程度发挥线程池的作用以及避免踩坑. 一.线程池 ...

  5. Java 线程池框架核心代码分析--转

    原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...

  6. Java线程池使用说明

    Java线程池使用说明 转自:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极 ...

  7. (转载)JAVA线程池管理

    平时的开发中线程是个少不了的东西,比如tomcat里的servlet就是线程,没有线程我们如何提供多用户访问呢?不过很多刚开始接触线程的开发攻城师却在这个上面吃了不少苦头.怎么做一套简便的线程开发模式 ...

  8. 四种Java线程池用法解析

    本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...

  9. Java线程池的几种实现 及 常见问题讲解

    工作中,经常会涉及到线程.比如有些任务,经常会交与线程去异步执行.抑或服务端程序为每个请求单独建立一个线程处理任务.线程之外的,比如我们用的数据库连接.这些创建销毁或者打开关闭的操作,非常影响系统性能 ...

随机推荐

  1. struts2 DMI

    在使用DMI(动态方法调用)的时候要注意struts.xml配置时要把 <constant name="struts.enable.DynamicMethodInvocation&qu ...

  2. Http概述(一)

    Http使用的是可靠的数据传输协议,因此即使数据来自地球的另一端,也能够确保数据在传输过程中不会被损坏或产生混乱. 这样用户在访问信息时就不用担心其完整性了. web服务端与服务器是如何通信的 Web ...

  3. 【讲义提纲】以一个实战新闻cms增删改查demo为例,给学院国创队伍培训php

    PHP实战基础——以一个新闻cms的增删改查为例 一.        环境配置 二.        数据库创建 三.        增删改查demo 连接数据库 <?php $link=mysq ...

  4. C#读取网络流,读取网络上的js文件

    写博客的目的就是让其他人少走弯路. C#读取网络上的流和js文件出现的问题 一开始看了今天博客园上的推荐文章,用C#+HtmlAgilityPack+XPath带你采集数据(以采集天气数据为例子),然 ...

  5. 误人子弟的网络,谈谈HTTP协议中的短轮询、长轮询、长连接和短连接

    引言 最近刚到公司不到一个月,正处于熟悉项目和源码的阶段,因此最近经常会看一些源码.在研究一个项目的时候,源码里面用到了HTTP的长轮询.由于之前没太接触过,因此LZ便趁着这个机会,好好了解了一下HT ...

  6. SQLite剖析之内核研究

    先从全局的角度把握SQLite内核各个模块的设计和功能.SQLite采用了层次化.模块化的设计,而这些使得它的可扩展性和可移植性非常强.而且SQLite的架构与通用DBMS的结构差别不是很大,所以它对 ...

  7. [POJ2404]Jogging Trails(中国旅行商问题)(一般图的匹配——状压DP)

    题目:http://poj.org/problem?id=2404 题意:有个n(n<=15)的点和m条无向边,每条边都有自己的权值.现在你要从某个点出发,每条边可以经过多次但要保证每条边至少走 ...

  8. C++成员变量的初始化顺序问题

    问题来源: 由于面试题中,考官出了一道简单的程序输出结果值的题:如下, class A { private: int n1; int n2; public: A():n2(0),n1(n2+2){} ...

  9. 【心得】在脱离TFS的情况下,如何解除TFS绑定?

    我们知道在有TFS的情况下,在文件-源代码管理-高级中可以解除TFS绑定. 但是如果我们出差去了,拿着笔记本电脑,打开解决方案的时候,会总是提示无法连接TFS,并且在源代码管理处尝试解除的时候也提示无 ...

  10. C++中函数的默认参数和C语言中volatile的学习

    1.函数默认参数 1 int func(int a,int b=10) 2 { 3 return a*b; 4 } 5 6 int main() 7 { 8 int c=func(2); 9 cout ...