Java 自定義執行緒池的執行緒工廠
本文分享建立執行緒工廠 ThreadFactory 的三種方式,以方便大家快速建立執行緒池,並通過執行緒工廠給每個創建出來的執行緒設定極富業務含義的名字。
執行緒池大小考慮因素
由於需要自定義執行緒池,故這裡先介紹執行緒池大小如何設定最為合理。我們需要分析計算環境、資源預算和任務的特性,例如,考慮一下在部署的伺服器上有多少個CPU,記憶體是多大,任務是計算密集型、I/O密集型還是二者皆可。
對於計算密集型的任務,在擁有N(CPU)個處理器的伺服器上,當執行緒池的大小為N+1時,通常能實現最優的利用率。對於包含I/O操作或者其它阻塞操作的任務,由於執行緒並不會一直執行,因此執行緒池的規模應該更大,參考值可以設定為2**N(CPU)。執行緒池資源並不是唯一影響執行緒池大小的資源,還包括記憶體、檔案控制代碼、套接字控制代碼和資料庫連線等。在觀察任務執行情況和系統負載、資源利用率之後,請酌情調整。
在Java中,可以使用如下程式碼獲取CPU的數量:
int N(CPU)= Runtime.getRuntime().availableProcessors();
基於此可以設定合適的執行緒池數量,提高系統穩定性和吞吐率。
Spring CustomizableThreadFactory
此方式是利用Spring 框架提供的輪子 CustomizableThreadFactory。
ThreadFactory springFactory = new CustomizableThreadFactory("spring-pool-"); ExecutorService exec = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(100), springFactory); exec.submit(() -> { logger.info("--記憶中的顏色是什麼顏色---"); });
guava ThreadFactoryBuilder
使用Google 開源框架guava提供的 ThreadFactoryBuilder 可以快速給執行緒池裡的執行緒設定有意義的名字。
ThreadFactory guavaFactory = new ThreadFactoryBuilder().setNameFormat("guava-pool-").build(); ExecutorService exec = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(100),guavaFactory ); exec.submit(() -> { logger.info("--記憶中的顏色是什麼顏色---"); });
此方法需要引入如下guava maven座標:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
Apache commons-lang3 BasicThreadFactory
Apache commons-lang3 提供的 BasicThreadFactory.
ThreadFactory basicFactory = new BasicThreadFactory.Builder()
.namingPattern("basicFactory-pool-").build();
ExecutorService exec = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(100),basicFactory );
exec.submit(() -> {
logger.info("--記憶中的顏色是什麼顏色---");
});
使用的列隊是無界佇列LinkedBlockingQueue。如果當前執行任務數量大於核心執行緒數,此時再提交任務就在新增到阻塞佇列中等待執行,直到有可用執行緒。溫馨提示,無界阻塞佇列可能消耗很大的記憶體資源。值得注意的是,如果使用了無界的任務佇列,執行緒池最大數量這個引數就沒什麼效果了。
優雅的自定義執行緒工廠
除了使用第三方jar包之外,我們也可以基於ThreadPoolExecutor優雅地自定義ThreadFactory,並根據自己的需要來操作執行緒,下面是例項程式碼:
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
@Slf4j
public class ThreadPoolStudy {
public static void main(String[] args) {
ExecutorService executor = initExector();
// 若i>0,則不會丟擲異常資訊。設為0是為了驗證如何捕捉異常
for (int i = 0; i < 10; i++) {
DivTask myTask = new DivTask(100, i);
Future future = executor.submit(myTask);
try {
// 非阻塞方法,呼叫get是為了避免任務執行異常被吞掉
future.get();
} catch (Exception e) {
log.error("任務執行異常,", e);
}
}
executor.shutdown();
log.info("執行緒池馬上關閉,不再接收新任務。");
}
// 自定義任務,用於求兩數相除的結果
static class DivTask implements Runnable {
int a, b;
public DivTask(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
log.info("計算結果:{}", a / b);
}
}
/**
* 初始化執行緒池
*
* @return
*/
private static ExecutorService initExector() {
ThreadFactory guavaFactory = new ThreadFactoryBuilder().setNameFormat("guava-pool-").build();
ExecutorService executor = new ThreadPoolExecutor(1, 64,
30L, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(), guavaFactory);
return executor;
}
}
任務執行完畢後,請關閉執行緒池,及時降低資源損耗。
小結
作為程式設計師,要有“刨根問底”的精神。知其然,更要知其所以然。這篇文章希望能幫助你對自定義執行緒工廠抽絲剝繭,還原背後的原理。
Reference
讀後有收穫,小禮物走一走,請作者喝咖啡。
讚賞支援