常用的線程池創建
一、爲什麼要使用線程池
1.線程池多數使用在高併發的情況下,使用線程池可以重複利用已經創建的線程,減少了線程創建和銷燬時的資源消耗;
2.由於沒有頻繁創建和銷燬線程,使得系統效率可以大大提升;
3.可以控制線程數量,更有效的利用系統資源;
二、線程池執行流程
三、常用創建方式
先了解下線程池比較重要的幾個參數
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) { }
【int corePoolSize】核心線程數
【int maximumPoolSize】允許的最大線程數
【long keepAliveTime】空閒線程存活時間
【TimeUnit unit】keepAliveTime參數的時間單位
【BlockingQueue<Runnable> workQueue】任務隊列,線程池達到了核心線程數,其他線程又是活躍狀態,任務進入此隊列
【ThreadFactory threadFactory】定製線程的創建過程
【RejectedExecutionHandler handler】拒絕策略, 當workQueue隊滿時,採取的措施
1.CachedThreadPool
調用ThreadPoolExecutor的構造,設置最大線程數爲Integer最大值,無限制的線程池,理論上,如果其他線程均爲活躍狀態,並非空閒,只要接到任務需求,就會創建新線程,所以相對FixedThreadPool更快一點。
//源碼
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
如何創建:
public class CachedThreadPoolTest {
public static void main(String[] args) {
ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
cacheThreadPool.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currentThread().getName()
+ ":" + System.currentTimeMillis());
//每條線程執行至少10ms,則會創建10條線程
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
執行結果:
對比代碼:
public class CachedThreadPoolTest {
public static void main(String[] args) {
ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
cacheThreadPool.execute(new Runnable(){
@Override
public void run(){
System.out.println(Thread.currentThread().getName()
+ ":" + System.currentTimeMillis());
}
});
//循環啓動十次任務,但線程已經執行完畢,一直是同一條線程執行任務
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
執行結果:
2.FixedThreadPool
啓動固定線程數量的線程池,調用ThreadPoolExecutor的構造,將corePoolSize和maximumPoolSize設置相同值,使線程數量固定爲給定參數值,空餘線程等待時間設置爲0,無界任務隊列LinkedBlockingQueue,數量固定,即使沒有任務,空閒的線程也不會被回收。
//源碼
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
如何創建:
public class FixedThreadPoolTest {
public static final int FLAG = 15;
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
Future<?> result = newFixedThreadPool.submit(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ ":" + FLAG);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
執行結果:
3. SingleThreadPool:
創建單一線程池:只有一條線程,調用ThreadPoolExecutor構造, 將coreThreadSize及maximumPoolSize設置爲1,後續線程任務將在任務隊列中排隊。
//源碼
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
如何創建:
public class SingleThreadPoolExecutor {
public static void main(String[] args) {
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
singleThreadPool.execute(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ ":" + System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
執行結果:
4.ScheduledThreadPool
可以實現延遲執行或定時執行任務。
如何創建延時任務
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduleExecutorService = Executors.newScheduledThreadPool(3);
//延時執行:
System.out.println(System.currentTimeMillis());
scheduleExecutorService.schedule(new Runnable(){
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}, 3, TimeUnit.SECONDS);//延遲3秒執行
}
執行結果:
如何創建延時定時任務
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduleExecutorService = Executors.newScheduledThreadPool(3);
//延遲加定時執行
System.out.println(System.currentTimeMillis());
scheduleExecutorService.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}, 1, 3, TimeUnit.SECONDS);//延遲1秒,每三秒執行一次
}
}
執行結果:
5. ForkJoinPool
分段執行線程任務,多用於單個線程任務有參數限制,需要進行多段任務同時進行,以提高效率的情況。
代碼演示:
public class ForkJoinPoolTest {
public static void main(String[] args) {
int[] arr = new int[120];
int total = 0;
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random()*20);
total += arr[i];
}
System.out.println("初始化120個數字累計和爲:" + total);
ForkJoinPool forkJoinPool = new ForkJoinPool();
SumTask task = new SumTask(arr, 0, arr.length);
Future<Integer> future = forkJoinPool.submit(task);
try {
System.out.println("分段計算120個數字累計和爲:" + future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class SumTask extends RecursiveTask<Integer>{
private static final int THRESHOLD = 50;//最多允許計算50個數
private int[] arr;
private int start;
private int end;
public SumTask(int[] arr, int start, int end) {
this.arr = arr;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
Integer sum = 0;
if( end - start < THRESHOLD){
for (int i = start; i< end; i++) {
sum += arr[i];
}
return sum;
}else{
int middle = (end + start)/2;
SumTask left = new SumTask(arr,start,middle);
SumTask right = new SumTask(arr,middle,end);
left.fork();
right.fork();
return left.join() + right.join();
}
}
}
執行結果:
6. WorkStealingPool
可以設置並行數量的線程池,不設置參數則默認爲電腦的CPU線程數量。
如何創建:
public class WorkStealingPoolTest {
public static void main(String[] args) {
//設置並行數量爲2,最多兩個線程執行任務
ExecutorService workStealingPool = Executors.newWorkStealingPool(2);
for (int i = 0; i < 10; i++) {
final int count = i;
workStealingPool.submit(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ "正在執行任務" + count + ",時間:"
+ System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
//程序延遲10秒結束,以觀察結果
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執行結果:
四、線程池內部任務隊列
1.ArrayBlockingQueque:
基於數組實現的阻塞隊列,內部維護了一個定長數組,以便緩存隊列中的數據對象,內部還保存兩個整形變量,在生產者生產數據和消費者獲取數據的過程中,沒有進行鎖分離,用的是同一把鎖,因此生產者和消費者無法並行,效率相對較低,在初始化時數組必須指定長度,即必須指定隊列大小。
2.LinkedBlockingQueue:
無邊界鏈式阻塞隊列,用在【FixedThreadPool】及【SingleThreadPool】內部,其內部使用鏈表實現,因爲生產者和消費者使用的是獨立的鎖對象,生產用的是【putLock】消費用的是【takeLock】,所以能夠更加高效的處理併發數據,生產者和消費者可以並行操作隊列中的數據,提高併發性能,初始化過程中可以不指定隊列大小,默認是Integer最大值。
3.DelayedWorkQueue:
延時阻塞隊列 ,用在【ScheduledThreadPool】內部,只有當延時的時間到了,纔會從隊列中獲取元素,因此,再向隊列中插入數據的操作(即生產者)永遠不會被阻塞,只有消費者在獲取數據的時候,可能被阻塞。
4.PriorityBlockingQueue:
基於優先級的阻塞隊列,優先級通過傳入的參數決定,該隊列不會阻塞生產者,只有在消費者沒有數據可以取的時候,阻塞消費者,因此,該隊列的生產者插入數據的速度,必須小於消費者獲取數據的速度,否則時間長會耗盡所有可用的堆內存,內部控制線程同步時,使用的是公平鎖(FIFO-First Input First Output)。
5.SynchronousQueue:無緩衝等待隊列,用在【CacheThreadPool】內部,沒有容量,由內部類【SNode]】對象存儲元素,必須等隊列中的添加元素被消費者消費後才能繼續添加新的元素。
五、關於拒絕策略
1.ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出【RejectExecutionException】異常(默認處理方式)
2.ThreadPoolExecutor.DiscardPolicy:丟棄任務,不拋出以常。
3.ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)。
4.ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務,即由主線程處理任務。
六、如何合理的分配線程數量
此內容來自一個聲音親切熱心可愛的小姐姐,十分感謝
1.CPU密集型:一般指計算比較頻繁的業務情況,【線程數量 = CPU核數 + 1】以減少線程上下文切換。
2.IO密集型:一般指查詢操作比較頻繁的業務情況,分兩種,任務線程並不是一直都在執行任務,應儘可能多的配置線程數量,如【CPU核數 * 2 】,另一種,大部分線程均爲阻塞狀態,需要配置多一點的線程數,參考公式【CPU核數 / (1-阻塞係數)】,阻塞係數一般在【0.8~0.9】之間。