浅谈java线程池

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

对线程池的误解

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

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

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

首先验证下个人理解:ide

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。this

个人机子上的测试结果:spa

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  减少单个线程的栈大小

相关文章
相关标签/搜索