3.9 創(chuàng)建線程以及線程池時候要指定與業(yè)務相關的名字冶共,以便于追溯問題
日常開發(fā)中當一個應用中需要創(chuàng)建多個線程或者線程池時候最好給每個線程或者線程池根據(jù)業(yè)務類型設置具體的名字,以便在出現(xiàn)問題時候方便進行定位邑滨,下面就通過實例來說明不設置時候為何難以定位問題日缨,以及如何進行設置。
3.9.1創(chuàng)建線程需要帶線程名
下面通過簡單的代碼來說明不指定線程名稱為何難定位問題掖看,代碼如下:
public static void main(String[] args) {
//訂單模塊
Thread threadOne = new Thread(new Runnable() {
public void run() {
System.out.println("保存訂單的線程");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
throw new NullPointerException();
}
});
//發(fā)貨模塊
Thread threadTwo = new Thread(new Runnable() {
public void run() {
System.out.println("保存收獲地址的線程");
}
});
threadOne.start();
threadTwo.start();
}
如上代碼分別創(chuàng)建了線程one和線程two并且啟動執(zhí)行運行上面代碼可能會輸出如下:
從運行接口可知Thread-0
拋出了NPE異常匣距,那么單看這個日志根本無法判斷是訂單模塊的線程拋出的異常,首先我們分析下這個Thread-0
是怎么來的哎壳,這要看下創(chuàng)建線程時候的代碼:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
可知如果調(diào)用了沒有指定線程名字的方法創(chuàng)建了線程毅待,內(nèi)部會使用"Thread-" + nextThreadNum()
作為線程的默認名字,其中nextThreadNum代碼如下:
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
可知threadInitNumber是static變量归榕,nextThreadNum是static方法尸红,所以線程的編號是全應用唯一的并且是遞增的,另外這里由于涉及到了多線程遞增threadInitNumber也就是執(zhí)行讀取-遞增-寫入操作刹泄,而這個是線程不安全的所以使用了方法級別的synchronized進行同步外里。
當一個系統(tǒng)中有多個業(yè)務模塊而每個模塊中有都是用了自己的線程,除非拋出與業(yè)務相關的異常特石,否者比如上面拋出的NPE異常盅蝗,根本沒法判斷是哪一個模塊出現(xiàn)了問題,現(xiàn)在修改代碼如下:
static final String THREAD_SAVE_ORDER = "THREAD_SAVE_ORDER";
static final String THREAD_SAVE_ADDR = "THREAD_SAVE_ADDR";
public static void main(String[] args) {
// 訂單模塊
Thread threadOne = new Thread(new Runnable() {
public void run() {
System.out.println("保存訂單的線程");
throw new NullPointerException();
}
}, THREAD_SAVE_ORDER);
// 發(fā)貨模塊
Thread threadTwo = new Thread(new Runnable() {
public void run() {
System.out.println("保存收貨地址的線程");
}
}, THREAD_SAVE_ADDR);
threadOne.start();
threadTwo.start();
}
如上代碼在創(chuàng)建線程的時候給線程指定了一個與具體業(yè)務模塊相關的名字姆蘸,下面運行結(jié)果輸出為:
從運行結(jié)果就可以定位到是保存訂單模塊拋出了NPE異常墩莫,一下子就可以定位到問題。
3.9.2創(chuàng)建線程池時候也需要指定線程池的名稱
同理下面通過簡單的代碼來說明不指定線程池名稱為何難定位問題乞旦,代碼如下:
static ThreadPoolExecutor executorOne = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
static ThreadPoolExecutor executorTwo = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
public static void main(String[] args) {
//接受用戶鏈接模塊
executorOne.execute(new Runnable() {
public void run() {
System.out.println("接受用戶鏈接線程");
throw new NullPointerException();
}
});
//具體處理用戶請求模塊
executorTwo.execute(new Runnable() {
public void run() {
System.out.println("具體處理業(yè)務請求線程");
}
});
executorOne.shutdown();
executorTwo.shutdown();
}
運行代碼輸出如下結(jié)果:
同理我們并不知道是那個模塊的線程池拋出了這個異常贼穆,那么我們看下這個
pool-1-thread-1
是如何來的。其實是使用了線程池默認的ThreadFactory兰粉,翻看線程池創(chuàng)建的源碼如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
static class DefaultThreadFactory implements ThreadFactory {
//(1)
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
//(2)
private final AtomicInteger threadNumber = new AtomicInteger(1);
//(3)
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
//(4)
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
如上代碼DefaultThreadFactory的實現(xiàn)可知:
- 代碼(1)poolNumber是static的原子變量用來記錄當前線程池的編號是應用級別的故痊,所有線程池公用一個,比如創(chuàng)建第一個線程池時候線程池編號為1玖姑,創(chuàng)建第二個線程池時候線程池的編號為2愕秫,這里
pool-1-thread-1
里面的pool-1中的1就是這個值 - 代碼(2)threadNumber是線程池級別的慨菱,每個線程池有一個該變量用來記錄該線程池中線程的編號,這里
pool-1-thread-1
里面的thread-1中的1就是這個值 - 代碼(3)namePrefix是線程池中線程的前綴戴甩,默認固定為pool
- 代碼(4)具體創(chuàng)建線程符喝,可知線程的名稱使用
namePrefix + threadNumber.getAndIncrement()
拼接的。
從上知道我們只需對實現(xiàn)ThreadFactory并對DefaultThreadFactory的代碼中namePrefix的初始化做手腳甜孤,當需要創(chuàng)建線程池是傳入與業(yè)務相關的namePrefix名稱就可以了协饲,代碼如下:
// 命名線程工廠
static class NamedThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
NamedThreadFactory(String name) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
if (null == name || name.isEmpty()) {
name = "pool";
}
namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
然后創(chuàng)建線程池時候如下:
static ThreadPoolExecutor executorOne = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(), new NamedThreadFactory("ASYN-ACCEPT-POOL"));
static ThreadPoolExecutor executorTwo = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(), new NamedThreadFactory("ASYN-PROCESS-POOL"));
然后運行執(zhí)行結(jié)果如下:從 ASYN-ACCEPT-POOL-1-thread-1
就可以知道是接受鏈接線程池拋出的異常。
3.9.3總結(jié)
本節(jié)通過簡單的例子介紹了為何不給線程或者線程池起名字會給問題排查帶來麻煩缴川,然后通過源碼原理介紹線程和線程池名稱是默認名稱是如何來的茉稠,以及如何自定義線程池名稱,以便問題追溯把夸。
--------------------------------相約GitChat探討技術--------------------------------------
一而线、常用開源框架 Spring 和 Tomcat 擴展接口揭秘
評價一個框架是否優(yōu)秀,其中必有一點是看該框架是否留足了可擴展的接口恋日。我們在實際做項目或者研發(fā)框架時膀篮,很多情況下就是在框架留出的擴展接口上進行定制,所以很有必要對這些框架留出了哪些擴展點岂膳,這些擴展點是干啥用的有個心知肚明的了解誓竿。
本 Chat 將針對 Spring 和 Tomcat 擴展點進行介紹,主要內(nèi)容包括:
對 Spring 框架在容器刷新(Refresh 階段)闷营,創(chuàng)建 Bean(getBean)烤黍,容器銷毀(destory)階段中的擴展接口進行講解;
-
對 Tomcat 中的 ContextLoaderListener 擴展接口進行講解傻盟,并講解 Webx 框架和 SpringMVC 框架如何使用它,從而讓 Tomcat 與應用框架聯(lián)系起來嫂丙。
## 二娘赴、SpringBoot核心模塊原理分析Chat
最近微服務很火,SpringBoot 以其輕量級跟啤,內(nèi)嵌 Web 容器诽表,一鍵啟動,方便調(diào)試等特點被越來越多的微服務實踐者所采用隅肥。然而知其然還要知其所以然竿奏,本節(jié)就來講解 SpringBoot 的核心模塊的實現(xiàn)原理,這些內(nèi)容在面試的時候也是會被經(jīng)常問到的:
spring-boot-load 模塊腥放,正常情況下一個類加載器只能找到加載路徑的jar包里面當前目錄或者文件類里面的*.class文件泛啸,SpringBoot 允許我們使用 java -jar archive.jar 運行包含嵌套依賴 jar 的 jar 或者 war 文件,那么 SpringBoot 是如何實現(xiàn)的那秃症?
spring-boot-autoconfigure 模塊候址,Auto-configuration 是 SpringBoot 在 Spring 的基礎上提供的一個自動掃描 jar 包里面指定注解的類并注入到 Spring 容器的功能組件吕粹。
-
spring-boot 模塊,提供了一些特性用來支持 SpringBoot 中其它模塊岗仑。
歡迎長按識別二維碼加入本chat
三匹耕、Java 類加載器揭秘Chat
類加載器作為 JVM 加載字節(jié)碼到內(nèi)存中的媒介,其重要性不言而喻荠雕,另外在職場面試時候也會被頻繁的問道稳其,了解類加載器的原理,能靈活的自定義類加載器去實現(xiàn)自己的功能顯得尤為重要炸卑。
主要內(nèi)容:
講解 Java 中自帶的三種類加載器欢际,以及構(gòu)造原理
講解類加載器原理
講解一種特殊的與線程相關類加載器
講解 Tomcat 框架中多級類加載器的實現(xiàn)原理
-
講解如何自定義類加載器實現(xiàn)模塊隔離
歡迎長按識別二維碼加入本chat