線程池(重點) 線程池:三大方法、七大參數、四種拒絕策略 池化技術 程式的運行,本質:占用系統的資源!優化資源的使用!-> 池化技術(線程池、連接池、對象池......);創建和銷毀十分消耗資源 池化技術:事先準備好一些資源,有人要用就拿,拿完用完還給我。 線程池的好處: 1、降低資源消耗 2、提高 ...
線程池(重點)
線程池:三大方法、七大參數、四種拒絕策略
池化技術
程式的運行,本質:占用系統的資源!優化資源的使用!-> 池化技術(線程池、連接池、對象池......);創建和銷毀十分消耗資源
池化技術:事先準備好一些資源,有人要用就拿,拿完用完還給我。
線程池的好處:
1、降低資源消耗
2、提高相應速度
3、方便管理
線程復用、可以控制最大併發數、管理線程
線程池:三大方法
1、newSingleThreadExecutor
單列線程池,只有一條線程;
單例線程池配合callable使用,註意需要在程式運行結束後關閉線程池
package org.example.pool;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//Executors->工具類
public class Demo01 {
public static void main(String[] args) {
TestCallable able = new TestCallable();
//三大方法
ExecutorService threadPool = Executors.newSingleThreadExecutor();//單個線程
try {
for (int i = 0; i < 10; i++) {
FutureTask<String> task = new FutureTask<String>(able);
//線程池創建線程
threadPool.execute(task);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//線程池用完,程式結束,關閉線程池
threadPool.shutdown();
}
// Executors.newFixedThreadPool(5);//固定線程池大小
// Executors.newCachedThreadPool();//可伸縮的線程池
}
}
class TestCallable implements Callable<String> {
Lock lock = new ReentrantLock();
@Override
public String call() throws Exception {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + ":ok");
return Thread.currentThread().getName() + ":ok";
}
}
註意結果圖
在單例線程池中,運行結果發現都是同一條線程在操作資源,整個線程池中只有一條pool-1-thread-1線程。
2、newFixedThreadPool
同樣的代碼,將線程池切換為固定大小的線程池,設置為5條,這樣跑出來的結果又不一樣
由於設置了線程休眠,所以會導致比較平均的結果出現,但是一般情況下都是五條線程搶占資源,每次結果都是不一定的,看那條線程處理的比較快搶占的比較多。
3、newCachedThreadPool
同樣的代碼,將線程池切換為可伸縮大小的線程池,這樣跑出來的結果又不一樣
根據業務代碼生成具體條數的線程:如本次業務通過迴圈或其他因數,同時需要處理10條任務,那麼當你線程池中的第一條線程還未完成任務時就會生成一條新的線程來同步處理這些任務,只要你cpu處理速度夠快那麼理論最高可能同時生成一個具有10條線程(任務數)的一個線程池。
七大參數
源碼分析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
//和以上沒有區別,只是通過用戶調用來完成的,相當於new ThreadPoolExecutor(5, 5,....)
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
//設置了預設是0個線程,但是最大值可以達到大約21億條,設置了Integer.MAX_VALUE(約21億)
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,//如果通過Integer.MAX_VALUE來跑線程池一定會照成OOM(溢出)
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
通過觀察以上三大方法的創建線程池方式,可以發現,三大方法的本質都是調用ThreadPoolExecutor來創建的
public ThreadPoolExecutor(int corePoolSize,//核心線程池大小
int maximumPoolSize,//最大線程池大小
long keepAliveTime,//超時無調用釋放
TimeUnit unit,//超時單位
BlockingQueue<Runnable> workQueue,//阻塞隊列
ThreadFactory threadFactory,//線程工廠,創建線程的,一般不用動
RejectedExecutionHandler handler) {//拒絕策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
這就是為什麼阿裡巴巴開發規範中說不要使用Executors來創建線程池而是讓我們通過ThreadPoolExecutor來創建,其實就是讓我們通過瞭解線程池的本質來避免一些問題。
模擬銀行業務模塊模擬
package org.example.pool;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo02 {
public static void main(String[] args) {
//自定義線程池,工作中只會使用ThreadPoolExecutor
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
new ThreadPoolExecutor.AbortPolicy());//拒絕策略,即當阻塞隊列和線程池線程都已經最大化運行沒有任何位置可以處置接下來的元素了,就拒絕該元素進入並拋出異常
try {
//最大承載 隊列Queue+max
for (int i = 0; i < 10; i++) {
//線程池創建線程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + ":ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//線程池用完,程式結束,關閉線程池
threadPool.shutdown();
}
}
}
此時有10個任務,但是最大線程量只有5個,阻塞隊列量只有三個,所以註定會有兩個無法處理,此時觸發拒絕策略,拋出異常並且拒絕該任務。
可以看到執行了八個任務,通過五條不同的線程。執行了拒絕策略拋出了異常java.util.concurrent.RejectedExecutionException
四種拒絕策略
四種拒絕策略的描述
//AbortPolicy 隊列滿了,丟掉任務,拋出異常
//CallerRunsPolicy 哪條線程給的任務回到哪條線程去執行,線程池不執行
//DiscardPolicy 隊列滿了,丟掉任務,但不拋出異常
//DiscardOldestPolicy 隊列滿了,嘗試去和最早的競爭,如果沒成功,依舊丟棄任務,但不拋出異常
小結和拓展
瞭解:IO密集型、cpu密集型(調優)
/*
* 最大線程到底該如何定義
* 1、CPU 密集型,幾何就定義為幾,就可以保證cpu效率最高的
* 2、IO 密集型 > 判斷你程式中十分耗IO的線程,
* 程式 15個大小任務 io十分占用資源
* */