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线程占用太多资源?

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

首先验证下我的理解:

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

直接上测试用例:

 
 
  1. public class ThreadPoolTest extends TestCase { 
  2.     private static final int COUNT = 10000
  3.  
  4.     public void testThreadPool() throws InterruptedException { 
  5.         CountDownLatch countDownLatch = new CountDownLatch(COUNT); 
  6.         ExecutorService executorService = Executors.newFixedThreadPool(100); 
  7.         long bg = System.currentTimeMillis(); 
  8.         for (int i = 0; i < COUNT; i++) { 
  9.     Runnable command = new TestRunnable(countDownLatch); 
  10.     executorService.execute(command); 
  11.         } 
  12.         countDownLatch.await(); 
  13.         System.out.println("testThreadPool:" + (System.currentTimeMillis() - bg)); 
  14.     } 
  15.  
  16.     public void testNewThread() throws InterruptedException { 
  17.         CountDownLatch countDownLatch = new CountDownLatch(COUNT); 
  18.         long bg = System.currentTimeMillis(); 
  19.         for (int i = 0; i < COUNT; i++) { 
  20.     Runnable command = new TestRunnable(countDownLatch); 
  21.     Thread thread = new Thread(command); 
  22.     thread.start(); 
  23.         } 
  24.         countDownLatch.await(); 
  25.         System.out.println("testNewThread:" + (System.currentTimeMillis() - bg)); 
  26.     } 
  27.  
  28.     private static class TestRunnable implements Runnable { 
  29.         private final CountDownLatch countDownLatch; 
  30.  
  31.         TestRunnable(CountDownLatch countDownLatch) { 
  32.     this.countDownLatch = countDownLatch; 
  33.         } 
  34.  
  35.         @Override 
  36.         public void run() { 
  37.     countDownLatch.countDown(); 
  38.         } 
  39.     } 

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

我的机子上的测试结果:

testThreadPool:31
testNewThread:624

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

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

624ms/10000=62.4us

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

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

再上测试用例:

 
 
  1. public class ThreadPoolTest extends TestCase { 
  2.     public void testThread() throws InterruptedException { 
  3.         int i = 1
  4.         while (true) { 
  5.     Runnable command = new TestRunnable(); 
  6.     Thread thread = new Thread(command); 
  7.     thread.start(); 
  8.     System.out.println(i++); 
  9.         } 
  10.     } 
  11.  
  12.     private static class TestRunnable implements Runnable { 
  13.         @Override 
  14.         public void run() { 
  15.     try { 
  16.         Thread.sleep(1000); 
  17.     } catch (InterruptedException e) { 
  18.         e.printStackTrace(); 
  19.     } 
  20.         } 
  21.     } 

以上用例模拟每次请求都创建一个新线程处理请求,然后默认每个请求的处理时间为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  减小单个线程的栈大小

来源:51CTO
相关文章
|
13天前
|
Java 调度
Java并发编程:深入理解线程池的原理与实践
【4月更文挑战第6天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将从线程池的基本原理入手,逐步解析其工作过程,以及如何在实际开发中合理使用线程池以提高程序性能。同时,我们还将关注线程池的一些高级特性,如自定义线程工厂、拒绝策略等,以帮助读者更好地掌握线程池的使用技巧。
|
21天前
|
Java 程序员
java线程池讲解面试
java线程池讲解面试
38 1
|
16天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【4月更文挑战第3天】 在Java并发编程中,线程池是一种重要的资源管理工具,它能有效地控制和管理线程的数量,提高系统性能。本文将深入探讨Java线程池的工作原理、应用场景以及优化策略,帮助读者更好地理解和应用线程池。
|
11天前
|
Java
Java 并发编程:深入理解线程池
【4月更文挑战第8天】本文将深入探讨 Java 中的线程池技术,包括其工作原理、优势以及如何使用。线程池是 Java 并发编程的重要工具,它可以有效地管理和控制线程的执行,提高系统性能。通过本文的学习,读者将对线程池有更深入的理解,并能在实际开发中灵活运用。
|
30天前
|
监控 Java
Java并发编程中的线程池优化技巧
在Java并发编程中,线程池扮演着至关重要的角色。本文将深入探讨如何优化Java线程池,从线程池的创建与配置、任务队列的选择、拒绝策略的制定、线程池状态的监控等多个方面进行详细阐述。通过本文的阅读,您将了解到如何合理地利用线程池,提高系统的并发性能,从而更好地应对各种并发场景。
|
12天前
|
Java
Java并发编程:深入理解线程池
【4月更文挑战第7天】在现代软件开发中,多线程编程已经成为一种不可或缺的技术。为了提高程序性能和资源利用率,Java提供了线程池这一强大工具。本文将深入探讨Java线程池的原理、使用方法以及如何根据实际需求定制线程池,帮助读者更好地理解和应用线程池技术。
15 0
|
2天前
|
缓存 分布式计算 监控
Java并发编程:深入理解线程池
【4月更文挑战第17天】在Java并发编程中,线程池是一种非常重要的技术,它可以有效地管理和控制线程的执行,提高系统的性能和稳定性。本文将深入探讨Java线程池的工作原理,使用方法以及在实际开发中的应用场景,帮助读者更好地理解和使用Java线程池。
|
3天前
|
存储 缓存 监控
Java线程池
Java线程池
31 1
|
3天前
|
缓存 监控 Java
Java并发编程:线程池与任务调度
【4月更文挑战第16天】Java并发编程中,线程池和任务调度是核心概念,能提升系统性能和响应速度。线程池通过重用线程减少创建销毁开销,如`ThreadPoolExecutor`和`ScheduledThreadPoolExecutor`。任务调度允许立即或延迟执行任务,具有灵活性。最佳实践包括合理配置线程池大小、避免过度使用线程、及时关闭线程池和处理异常。掌握这些能有效管理并发任务,避免性能瓶颈。
|
9天前
|
Java
Java并发编程:深入理解线程池
【4月更文挑战第10天】本文将深入探讨Java并发编程中的一个重要主题——线程池。我们将从线程池的基本概念入手,逐步深入到线程池的实现原理,以及如何在实际开发中合理使用线程池。通过本文的学习,你将能够理解线程池的核心原理,掌握线程池的使用技巧,以及避免常见的线程池使用误区。