本文分享創建線程工廠 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 = initExecutor();
// 若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("線程池馬上關閉,不再接收新任務。");
}
// 自定義任務,用於求兩數相除的結果
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 initExecutor() {
ThreadFactory guavaFactory = new ThreadFactoryBuilder().setNameFormat("guava-pool-").build();
ExecutorService executor = new ThreadPoolExecutor(1, 64,
30L, TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(), guavaFactory);
return executor;
}
}
任務執行完畢后,請關閉線程池,及時降低資源損耗。
小結
作為程序員,要有“刨根問底”的精神。知其然,更要知其所以然。這篇文章希望能幫助你對自定義線程工廠抽絲剝繭,還原背后的原理。