在java編程中,多線程是解決併發的重要手段,然而使用多線程同時創建和銷燬線程需要消耗資源,無限制的創建線程就會導致資源耗盡,系統崩潰。因此,當服務器內存和CPU不是絕對夠用時,採用線程池對多線程進行管理是合理的手段。線程池的好處(引用其他文章):
降低資源消耗,重複利用現有的線程降低線程的創建和銷燬對資源的消耗;
提高響應速率,當任務到達時,不用再等待線程的創建,利用現有線程即可立即執行任務;
提高線程的可管理性,線程是稀缺資源,如果無限制的創建線程,不僅會消耗系統資源,而且會降低系統的穩定性,使用線程池可以進行統一的分配、調優和監控。
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核數;當服務器只部署這一個應用並且只要這一個線程池,這估算可能合理,待確認。