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執行緒哪個效率高?
直接上測試用例:
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執行緒的那些事?Java執行緒
- 執行緒與執行緒池的那些事之執行緒池篇(萬字長文)執行緒
- java執行緒池趣味事:這不是執行緒池Java執行緒
- Java基礎之執行緒那些事Java執行緒
- Java執行緒池二:執行緒池原理Java執行緒
- Java多執行緒-執行緒池的使用Java執行緒
- java 執行緒池Java執行緒
- Java執行緒池Java執行緒
- SpringBoot執行緒池和Java執行緒池的實現原理Spring Boot執行緒Java
- java多執行緒9:執行緒池Java執行緒
- JAVA執行緒池的使用Java執行緒
- java--執行緒池--建立執行緒池的幾種方式與執行緒池操作詳解Java執行緒
- 【QT】 Qt多執行緒的“那些事”QT執行緒
- 搞懂Java執行緒池Java執行緒
- 【Java】【多執行緒】執行緒池簡述Java執行緒
- Java執行緒池一:執行緒基礎Java執行緒
- 多核和多執行緒那些事執行緒
- 詳解執行緒池的作用及Java中如何使用執行緒池執行緒Java
- Java併發 之 執行緒池系列 (1) 讓多執行緒不再坑爹的執行緒池Java執行緒
- 走進Java Android 的執行緒世界(二)執行緒池JavaAndroid執行緒
- Java執行緒池之ThreadPoolExecutorJava執行緒thread
- java-執行緒池(一)Java執行緒
- 速讀Java執行緒池Java執行緒
- Java執行緒池詳解Java執行緒
- Java執行緒池進階Java執行緒
- java執行緒池實踐Java執行緒
- Java執行緒池歸納Java執行緒
- Java 執行緒池詳解Java執行緒
- 深入淺出Java多執行緒(十二):執行緒池Java執行緒
- java多執行緒:執行緒池原理、阻塞佇列Java執行緒佇列
- 《Java 高階篇》七:執行緒和執行緒池Java執行緒
- Java利用執行緒工廠監控執行緒池Java執行緒
- Java面試必問之執行緒池的建立使用、執行緒池的核心引數、執行緒池的底層工作原理Java面試執行緒
- Java執行緒池的使用和原理Java執行緒
- JAVA執行緒池的原理及使用Java執行緒
- Java提供的幾種執行緒池Java執行緒
- 淺析Java中的執行緒池Java執行緒
- 獲取任意執行緒呼叫棧的那些事執行緒
- 詳解Java執行緒池的ctl(執行緒池控制狀態)【原始碼分析】Java執行緒原始碼