zoukankan      html  css  js  c++  java
  • Java线程池的那些事

    熟悉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线程池和创建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  减小单个线程的栈大小

      码农必须要加班?NO!

      知道码农们都想摆脱加班狗、外卖脸的称号,所以我们来了!

      我们做了一个能让程序员之间共享知识技能的APP,觉得可以颠覆程序员的工作方
    式!

      有人说我们痴心妄想,但我们不那么认为。

      为了能煽烂说我们痴心妄想的人的脸,现在我们急需程序员业内的牛哔-人物来给
    我们“号脉”!“诊断费”丰厚!毕竟我们不差钱儿,只是想做到最好!

      圈圈字典中讲到,牛哔-人物是指群成员数高于1000人的QQ群主或关注人数高于
    2000人的贴吧吧主或粉丝人数高于10000人的微博博主或成员数高于2000主题贴的版主
    或单帖阅读量高于2000博客主或人脉超级广的圈内红人。

      对于未能达标的未来大神们,我们只能含泪表示:蜀黍,咱们来日方长,这次暂
    时不约好吗?待他日你立地成神,我必生死相依!

      来?还是不来?

      圈圈互动 接头暗号:1955246408 (QQ)

  • 相关阅读:
    UVALive 7509 Dome and Steles
    HDU 5884 Sort
    Gym 101194H Great Cells
    HDU 5451 Best Solver
    HDU 5883 The Best Path
    HDU 5875 Function
    卡特兰数
    UVa 11729 Commando War 突击战
    UVa 11292 The Dragon of Loowater 勇者斗恶龙
    Spark Scala Flink版本对应关系
  • 原文地址:https://www.cnblogs.com/starliu/p/4747724.html
Copyright © 2011-2022 走看看