一. 介绍

  平时工作中可能会碰到排查多线程的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);
}
}

  

Java的每个Thread都希望拥有自己的名称的更多相关文章

  1. 2019 最新 Java 核心技术教程,都在这了!

    Java技术栈 www.javastack.cn 优秀的Java技术公众号 以下是Java技术栈微信公众号发布的所有关于 Java 的技术干货,会从以下几个方面汇总,本文会长期更新. Java 基础篇 ...

  2. Java多线程01(Thread类、线程创建、线程池)

    Java多线程(Thread类.线程创建.线程池) 第一章 多线程 1.1 多线程介绍 1.1.1 基本概念 进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于 ...

  3. java 并发编程——Thread 源码重新学习

    Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...

  4. 2.JAVA编程思想——一切都是对象

    一切都是对象 欢迎转载.转载请标明出处:http://blog.csdn.net/notbaron/article/details/51040221 虽然以C++为基础,但 Java 是一种更纯粹的面 ...

  5. JAVA多线程(一) Thread & Runnable

    githut代码地址:https://github.com/showkawa/springBoot_2017/tree/master/spb-demo/spb-brian-query-service/ ...

  6. 对于不平凡的我来说,从小我就在想为啥别人就什么都能拥有,而看看自己却什么都没有,对于原来的我就会抱怨爸妈怎么没有别人父母都能给自己想要的,可我从未想过父母的文化只有小学,其实父母内心也有太多的辛酸,所以我不甘愿如此,从此让我在大学里面直接选择一个让我巨大的转折————IT。

    对于不平凡的我来说,从小我就在想为啥别人就什么都能拥有,而看看自己却什么都没有,对于原来的我就会抱怨爸妈怎么没有别人父母都能给自己想要的,可我从未想过父母的文化只有小学,其实父母内心也有太多的辛酸,所 ...

  7. 【性能优化】面试官:Java中的对象都是在堆上分配的吗?

    写在前面 从开始学习Java的时候,我们就接触了这样一种观点:Java中的对象是在堆上创建的,对象的引用是放在栈里的,那这个观点就真的是正确的吗?如果是正确的,那么,面试官为啥会问:"Jav ...

  8. 《连载 | 物联网框架ServerSuperIO教程》1.4种通讯模式机制。附小文:招.NET开发,结果他转JAVA了,一切都是为了生活

    参考文章: 1.SuperIO通讯框架介绍,含通信本质 2.C#跨平台物联网通讯框架ServerSuperIO(SSIO) 一.感慨 上大学的时候,没有学过C#,花了5块钱在地坛书市买了一本教程,也就 ...

  9. JAVA下的Thread.sleep方法一定要try

    try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } 不同于C#,JAVA里的Thre ...

随机推荐

  1. Mac自带编码转换工具iconv

    iconv --help Usage: iconv [OPTION...] [-f ENCODING] [-t ENCODING] [INPUTFILE...] or: iconv -l Conver ...

  2. 关于LinearLayout设置权重后width或height不设置0dp的影响说明

    摘要 平时没那么注意LinearLayout布局时权重的问题,设置了权重属性后,通常建议将width或height的属性值设置为0dp,有时候设置权重后,还是习惯将width或height的属性设置为 ...

  3. Linux 搭建nginx的PID

    pid logs/nginx.pid 安装的时候就是没有,其实在启动 nginx 时自动生成的 里面存放的是 当前 nginx 住进程的 ID 号:所以在配置文件中指定pidpid /usr/loca ...

  4. PLDroidPlayer 是七牛推出的一款免费的适用于 Android 平台的播放器 SDK,采用全自研的跨平台播放内核,拥有丰富的功能和优异的性能,可高度定制化和二次开发。 https://developer.qiniu.com/pili/sdk/…

    PLDroidPlayer PLDroidPlayer 是一个适用于 Android 平台的音视频播放器 SDK,可高度定制化和二次开发,为 Android 开发者提供了简单.快捷的接口,帮助开发者在 ...

  5. [bzoj2088]P3505 [POI2010]TEL-Teleportation

    洛谷 bzoj 用了分层图的思想 题意 给一张图,要求你再尽可能的多连边,使得从1到2至少要经过5条边 没啥复杂的公式,讲解都在注释里 #include<cstdio> #include& ...

  6. C. K-Complete Word(小小的并查集啦~)

    永久打开的传送门 \(\color{Pink}{-------------分割-------------}\) \(n最大有2e5,那么暴力一定不行,找规律\) \(我们发现第i位的字符一定和第i+k ...

  7. Git 获取远程仓库指定分支内容

    1. 在本地一个空的文件夹中 git init  (生成本地仓库) 2. 在刚刚的文件夹中随便建立一个文件 ,git add . (为了生成分支)(提交到暂存区) 3. git commit -m'1 ...

  8. Cordova 浅析架构的原理

    因为项目使用了Cordova,也使用了很长时间.至于有很多hybride框架,为什么我们使用Cordova,这里不做过多的叙述,我们也是根据项目需求来选定的,需要及时更新.还要输出别人SDK等.没有最 ...

  9. idea设置配置提示模板

    File-->Settings-->LIve Templates-->+-->Template Group(模板名称)-->Live Template

  10. 《深入理解Java虚拟机》第 3 版里面到底多了哪些知识点?本文竟然得到了本书作者的认可!

    这是why的第 47 篇原创文章 荒腔走板 大家好,我是 why.老规矩,先是简短的荒腔走板聊聊生活. 上面的图是前几天拍的,那天晚上下班后,刚刚走进小区就看到了这一轮弯月和旁边那一颗特别特别亮的星星 ...