一. 介紹
平時工作中可能會碰到排查多線程的bug,而在排查的時候,如果線程(單個線程或者是線程池的線程)沒有一個比較明確的名稱,那么在排查的時候就比較頭疼,因為排查問題首先需要找出“問題線程”,如果連“問題線程”都找不到,就很難找出問題原因,本文就針對多線程中涉及到的線程池、線程組、線程名稱,介紹如果對其進行設置名稱,方便排查問題時快速定位。
二. 設置線程名稱
2.1 使用Thread+Runnable接口形式
如果是使用實現Runnable接口,然后使用Thread構造器來直接創建線程時,有兩種方式設置線程名稱:
1.在調用Thread的構造器時,傳入第二個參數即可,構造器定義如下
Thread Thread(Runnable target, String threadName)
2.調用Thread對象的setName方法,設置線程名稱即可;
上面兩種方法的示例代碼如下:
package cn.ganlixin;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
@Slf4j
public class DefineThreadName {
/**
* 不設置線程名稱,使用默認的線程名稱
*/
@Test
public void defaultThreadName() {
new Thread(() -> {
String threadName = Thread.currentThread().getName();
String threadGroupName = Thread.currentThread().getThreadGroup().getName();
long threadId = Thread.currentThread().getId();
log.info("threadName:{}, threadGroupName:{}, threadId:{}", threadName, threadGroupName, threadId);
}).start();
// 輸出 threadName:Thread-1, threadGroupName:main, threadId:13
}
/**
* 自定義線程的名稱
*/
@Test
public void customThreadName() {
// 方式一:指定Thread構造方法的第二個參數,也就是線程的名稱
new Thread(() -> {
String threadName = Thread.currentThread().getName();
String threadGroupName = Thread.currentThread().getThreadGroup().getName();
long threadId = Thread.currentThread().getId();
log.info("threadName:{}, threadGroupName:{}, threadId:{}", threadName, threadGroupName, threadId);
}, "my-custom-thread-name-1").start();
// 輸出 threadName:my-custom-thread-name-1, threadGroupName:main, threadId:13
// 方式二:使用Thread對象的setName方法,設置線程名稱
Thread thread = new Thread(() -> {
String threadName = Thread.currentThread().getName();
String threadGroupName = Thread.currentThread().getThreadGroup().getName();
long threadId = Thread.currentThread().getId();
log.info("threadName:{}, threadGroupName:{}, threadId:{}", threadName, threadGroupName, threadId);
});
thread.setName("my-custom-thread-name-2");
thread.start();
// 輸出 threadName:my-custom-thread-name-2, threadGroupName:main, threadId:14
}
}
2.2 繼承Thread類的形式
如果是繼承Thread類,那么可以在子類中調用Thread僅接受一個字符串作為線程名稱的構造器,像下面這么做:
package cn.ganlixin;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
@Slf4j
public class DefineThreadName {
/**
* 自定義的繼承自Thread的線程類
*/
private static class MyThread extends Thread {
private MyThread(String threadName) {
super(threadName); // Thread有一個構造器接收一個字符串類型的參數,作為線程名稱
}
@Override
public void run() {
// 因為繼承自Thread,所以下面可以直接調用這些方法,而不需要通過Thread.currentThread()獲取當前線程
String threadName = getName();
String threadGroupName = getThreadGroup().getName();
long threadId = getId();
log.info("threadName:{}, threadGroupName:{}, threadId:{}", threadName, threadGroupName, threadId);
}
}
/**
* 測試設置、更改線程名稱
*/
@Test
public void testInheritThread() {
MyThread t1 = new MyThread("my-extends-thread-name-1");
t1.start();
// 輸出 threadName:my-extends-thread-name-1, threadGroupName:main, threadId:13
MyThread t2 = new MyThread("my-extends-thread-name-2");
t2.setName("changed-thread-name"); // 手動修改線程名稱
t2.start();
// 輸出 threadName:changed-thread-name, threadGroupName:main, threadId:14
}
}
三. 設置線程組的名稱
線程組名稱需要在創建線程組的時候進行指定,然后使用線程組的時候將線程組作為Thread類的構造器參數傳入即可,示例代碼如下:
package cn.ganlixin.name;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
@Slf4j
public class ThreadGroupName {
@Test
public void defineThreadGroupName() {
// 定義一個線程組,傳入線程組的名稱(自定義)
ThreadGroup threadGroup = new ThreadGroup("my-thread-group-name");
Runnable runnable = () -> {
String threadGroupName = Thread.currentThread().getThreadGroup().getName();
String threadName = Thread.currentThread().getName();
long threadId = Thread.currentThread().getId();
log.info("threadGroupName:{}, threadName:{}, threadId:{}", threadGroupName, threadName, threadId);
};
Thread t1 = new Thread(threadGroup, runnable);
t1.start();
// 輸出 threadGroupName:my-thread-group-name, threadName:Thread-1, threadId:13
// 第三個參數是線程名稱
Thread t2 = new Thread(threadGroup, runnable, "my-thread-name");
t2.start();
// threadGroupName:my-thread-group-name, threadName:my-thread-name, threadId:14
}
}
四. 設置線程池名稱
4.1 創建線程池的兩種途徑
創建線程池,有兩種方式:
1.實例化ThreadPoolExecutor來創建線程池,可以指定相關的參數,方法定義如下:

2.使用Executors的靜態方法創建線程池,實際是對ThreadPoolExecutor對象的創建過程進行了封裝,可用的方法定義如下:

上面的諸多定義中,提到了一個ThreadFactory,“線程工廠”,這是一個接口,定義了創建線程的統一規范,實現類需要重寫newThread方法,定義如下:
package java.util.concurrent;
public interface ThreadFactory {
Thread newThread(Runnable r);
}
當我們調用Executors或者使用ThreadPoolExecutor來創建線程池,如果沒有指定ThreadFactory,那么就會使用默認的Executors.DefaultThreadFactory:
static class DefaultThreadFactory 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;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
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;
}
}
如果我們要對線程池中的線程創建進行擴展,那么實現ThreadFactory接口,加入自己的擴展即可,此處對於線程池中線程的名稱進行設置,也是可以在這里實現。
4.2 自定義線程工廠(ThreadFactory)
自己實現ThreadFactory接口,可以參考Executors.DefaultThreadFactory,做一下細微的修改就行了,下面是我創建的NameableThreadFactory,意為“可命名的線程工廠”:
package cn.ganlixin.name;
import org.apache.commons.lang3.StringUtils;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 描述:
* 參照Executors.DefaultThreadFactory,自定義ThreadFactory實現類
*
* @author ganlixin
* @create 2020-05-23
*/
public class NameableThreadFactory implements ThreadFactory {
/**
* 對線程池的數量進行計數,注意是類屬性
*/
private static final AtomicInteger poolNumber = new AtomicInteger(1);
/**
* 線程組名稱
*/
private ThreadGroup group;
/**
* 對線程池中的線程數據進行計數,注意是實例屬性
*/
private final AtomicInteger threadNumber = new AtomicInteger(1);
/**
* 線程名稱的前綴
*/
private String namePrefix;
/**
* Executors.DefaultThreadFactory中默認的方式(設置線程組、線程名稱前綴)
*/
public NameableThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
}
/**
* 創建線程工廠,指定線程名稱前綴
*
* @param threadNamePrefix 線程名稱前綴
*/
public NameableThreadFactory(String threadNamePrefix) {
if (StringUtils.isBlank(threadNamePrefix)) {
throw new IllegalArgumentException("線程名稱的前綴不能為空");
}
// 線程組,仍舊使用舊規則
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
// 指定線程的名稱前綴,設置為傳入的名稱前綴
this.namePrefix = threadNamePrefix + "-";
}
/**
* 創建線程工廠,指定線程組、以及線程名稱前綴
*
* @param threadGroup 線程組實例
* @param threadNamePrefix 線程名稱前綴
*/
public NameableThreadFactory(ThreadGroup threadGroup, String threadNamePrefix) {
if (Objects.isNull(threadGroup)) {
throw new IllegalArgumentException("線程組不能為空");
}
if (StringUtils.isBlank(threadNamePrefix)) {
throw new IllegalArgumentException("線程名稱的前綴不能為空");
}
this.group = threadGroup;
this.namePrefix = threadNamePrefix + "-";
}
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;
}
}
進行測試,因為Executors和ThreadPoolExecutor的本質是一樣的,所以這里使用Executors進行測試,只需要在用到ThreadFactory的時候,引入自己的創建NameableThreadFactory即可:
package cn.ganlixin.name;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public class TestNameableThreadFactory {
@Test
public void test() throws InterruptedException {
ExecutorService executorService1 = Executors.newFixedThreadPool(3, new NameableThreadFactory("自定義線程池one"));
Runnable runnable = () -> {
String threadGroupName = Thread.currentThread().getThreadGroup().getName();
String threadName = Thread.currentThread().getName();
long threadId = Thread.currentThread().getId();
log.info("threadGroupName:{}, threadName:{}, threadId:{}", threadGroupName, threadName, threadId);
};
for (int i = 0; i < 3; i++) {
executorService1.submit(runnable);
}
// 輸出
// threadGroupName:main, threadName:自定義線程池one-1, threadId:14
// threadGroupName:main, threadName:自定義線程池one-2, threadId:15
// threadGroupName:main, threadName:自定義線程池one-3, threadId:16
Thread.sleep(100);
// 創建線程組
ThreadGroup threadGroup = new ThreadGroup("自定義線程組one");
ExecutorService executorService2 = Executors.newFixedThreadPool(3, new NameableThreadFactory(threadGroup, "自定義線程池two"));
for (int i = 0; i < 3; i++) {
executorService2.submit(runnable);
}
// 輸出:
// threadGroupName:自定義線程組one, threadName:自定義線程池two-1, threadId:16
// threadGroupName:自定義線程組one, threadName:自定義線程池two-2, threadId:17
// threadGroupName:自定義線程組one, threadName:自定義線程池two-3, threadId:18
Thread.sleep(1000);
}
}
