java線程池

在java編程中,多線程是解決併發的重要手段,然而使用多線程同時創建和銷燬線程需要消耗資源,無限制的創建線程就會導致資源耗盡,系統崩潰。因此,當服務器內存和CPU不是絕對夠用時,採用線程池對多線程進行管理是合理的手段。線程池的好處(引用其他文章):

  1. 降低資源消耗,重複利用現有的線程降低線程的創建和銷燬對資源的消耗;

  2. 提高響應速率,當任務到達時,不用再等待線程的創建,利用現有線程即可立即執行任務;

  3. 提高線程的可管理性,線程是稀缺資源,如果無限制的創建線程,不僅會消耗系統資源,而且會降低系統的穩定性,使用線程池可以進行統一的分配、調優和監控。

Java中線程池所有的類位於rt.jar中java.util.concurrent包中,線程池核心類是
ThreadPoolExecutor。下面是該類是使用示例:

package thread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPool {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        try {
            ThreadPoolExecutor threadPool = 
                    new ThreadPoolExecutor(5, 5, 0, TimeUnit.MILLISECONDS,  new LinkedBlockingQueue<Runnable>());
            Thread thread1 = new Thread(new MyRunnable(),"thread1");
            Thread thread2 = new Thread(new MyRunnable(),"thread2");
            Thread thread3 = new Thread(new MyRunnable(),"thread3");
            Thread thread4 = new Thread(new MyRunnable(),"thread4");
            Thread thread5 = new Thread(new MyRunnable(),"thread5");            
            threadPool.execute(thread1);
            threadPool.execute(thread2);
            threadPool.execute(thread3);
            threadPool.execute(thread4);
            threadPool.execute(thread5);
        } catch (IllegalArgumentException e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }

    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                Thread.sleep(2000);
                System.out.println("PP:"+Thread.currentThread().getName()+":ggg");
            } catch (InterruptedException e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }   
    }   
}
運行結果:
PP:pool-1-thread-2:ggg
PP:pool-1-thread-5:ggg
PP:pool-1-thread-1:ggg
PP:pool-1-thread-3:ggg
PP:pool-1-thread-4:ggg

在上面例子中使用ThreadPoolExecutor創建了可以併發執行5個線程的線程池,ThreadPoolExecutor類是線程池的核心類,繼承了AbstractExecutorService類,AbstractExecutorService類實現了ExecutorService接口,在concurrent包中線程池相關的類圖:
這裏寫圖片描述

ThreadPoolExecutor類構造函數如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
}

構造函數需要5個參數:
corePoolSize:核心線程池大小,當線程池小於corePoolSize時,對於新的任務就會創建新線程執行,當線程池數大小超過corePoolSize時,新提交的任務被放入workQueue中;

maximumPoolSize:最大線程池大小,線程池中併發執行的線程的最大數量,當maximumPoolSize大於corePoolSize時,且workQueue已滿時,新的任務就會創建新的線程執行;

keepAliveTime:線程池中超過corePoolSize大小的空閒線程存活時間;

unit:keepAliveTime單位;

workQueue:任務隊列。

但是在實際開發中,並不建議使用ThreadPoolExecutor構造函數創建線程池,java給出了一個Executors類,Executors類提供了幾個靜態方法,用來創建線程池

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

四種方法:
newFixedThreadPool:是創建固定大小的線程池,核心線程數量與最大線程數量一致;
newSingleThreadExecutor:是創建只能併發執行一個線程的線程池;
newScheduledThreadPool:創建一個可緩存的的線程池,並支持定時及週期性任務執行。
newCachedThreadPool:是創建緩存線程池,若線程池長度超過任務處理需求,則回收線程,否則創建新線程執行任務;

newFixedThreadPool、newSingleThreadExecutor方法比較好理解,都是創建固定大小的線程池;

newScheduledThreadPool比Timer更安全,功能更強大,示例:

//延遲3秒執行任務
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        scheduledExecutorService.schedule(new Runnable() {  
            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("delay 3 seconds");
            }
        }, 3, TimeUnit.SECONDS);

//延遲2秒後,每隔3秒執行一次
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("delay 2 seconds and rate 3 seconds");
            }
        }, 2, 3, TimeUnit.SECONDS);

newCachedThreadPool方法的最大線程池數量是Integer.MAX_VALUE,所有當併發任務量大時,線程池長度變大,有可能造成內存不足,系統崩潰,例如引用其他作者遇到的情況:使用無限大小線程池 newCachedThreadPool 可能遇到的問題

因此,創建線程池建議使用newFixedThreadPool,但是如何確定線程池的大小呢?需要結合服務器資源、業務需求等綜合考慮。查看其他作者的文章,線程池大小的確定要考慮應用系統的類型,應用系統類型一般分爲計算密集型和IO密集型。
計算密集型的應用線程池大小設置公式爲:
線程數 = cpu核數+1

IO密集型應用線程池大小設置公式爲:
公式1:線程數=cpu核數/(1-阻塞係數); 其中阻塞系統取值爲0.8-0.9參考

公式2:線程數=cpu核數*2+1 ;是否可行待確認;

公式3:線程數=((線程等待數據+線程cpu時間)/線程cpu時間)*cpu核數;當服務器只部署這一個應用並且只要這一個線程池,這估算可能合理,待確認。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章