淺談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  減少單個線程的棧大小

相關文章
相關標籤/搜索