Java的每个线程(Thread)希望拥有自己的职责名称

一. 线程Bug

  在我们平时得到工作中,可能会碰到排查由于使用多线程不当产生的bug,那么在排查的时候,如果线程过多而每一个没有一个比较明确的线程名称,那么我们在排查的时候就非常难受。

因为排查问题首先需要找到出现Bug“问题线程”,如果连“问题线程”我们都找不到(或者不好找),就很难找出问题产生的根本原因,所以,我们如果对线程设置了名称,那么就可以很方便对线程Bug进行快速定位。

二. 设置线程名称

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);
        }, "mfgcs-1").start();
 
        // 输出 threadName:mfgcs-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("mfgcs-2");
        thread.start();
        // 输出 threadName:mfgcs-2, threadGroupName:main, threadId:14
    }
}

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("mfgcs-1");
        t1.start();
        // 输出 threadName:mfgcs-1, threadGroupName:main, threadId:13
 
        MyThread t2 = new MyThread("mfgcs-2");
        t2.setName("changed-thread-name"); // 手动修改线程名称
        t2.start();
        // 输出 threadName:changed-thread-name, threadGroupName:main, threadId:14
    }
}

三. 设置线程组(ThreadGroup)的名称

  线程组的名称需要在创建线程组的时候进行设置,并且在使用线程组的时候,需要将线程组作为Thread类的构造器参数传入,示例代码参考:

package cn.ganlixin.name;
 
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
 
@Slf4j
public class ThreadGroupName {
 
    @Test
    public void defineThreadGroupName() {
        // 定义一个线程组,传入线程组的名称(mfgcs-group)
        ThreadGroup threadGroup = new ThreadGroup("mfgcs-group");
 
        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:mfgcs-group, threadName:Thread-1, threadId:13
 
        // 第三个参数是线程名称
        Thread t2 = new Thread(threadGroup, runnable, "mfgcs-1");
        t2.start();
        // threadGroupName:mfgcs-group, threadName:mfgcs-1, threadId:14
    }
}

四. 设置线程池名称

1. 创建线程池的两种途径

  1.通过对ThreadPoolExecutor的实例化来创建线程池,相关的参数看其源码可知:

Java的每个线程(Thread)希望拥有自己的职责名称

  2.使用Executors的静态方法创建线程池,实际是对ThreadPoolExecutor对象的创建过程进行了封装,可用的方法定义如下:在下面的方法中有提到了一个ThreadFactory(标红的那个),“线程工厂”,这是一个接口类,它定义了创建线程的统一规范,它的实现类需要重写newThread方法,定义代码如下:

Java的每个线程(Thread)希望拥有自己的职责名称
package java.util.concurrent;
 
public interface ThreadFactory {
    Thread newThread(Runnable r);
}

就是说,当我们在调用Executors或者使用ThreadPoolExecutor来创建线程池的时候,如果没有指定ThreadFactory,那么它就会使用默认的
Executors.DefaultThreadFactory:源码如下图所示

Java的每个线程(Thread)希望拥有自己的职责名称

所以,如果我们想要对线程池中的线程创建进行自定义扩展,那么需要实现ThreadFactory接口,然后加入自己的扩展即可,在这里就可以对线程池中线程进行名称设置。

2.自定义线程工厂(ThreadFactory)

  如果我们想自己实现ThreadFactory接口,那么可以参考上图中
Executors.DefaultThreadFactory的源码,自己稍微的做一些修改就行了,如下所示:MfgcsThreadFactory,意为“蜜蜂攻城狮的线程工厂“:

import org.apache.commons.lang3.StringUtils;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
 
/**
 * 描述:
 * 参照Executors.DefaultThreadFactory,自定义ThreadFactory实现类
 */
public class MfgcsThreadFactory 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 MfgcsThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
    }
 
    /**
     * 创建线程工厂,指定线程名称前缀
     *
     * @param threadNamePrefix 线程名称前缀
     */
    public MfgcsThreadFactory(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 MfgcsThreadFactory(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的时候,引入自己的定义的MfgcsThreadFactory即可:

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
@Slf4j
public class TestMfgcsThreadFactory {
 
    @Test
    public void test() throws InterruptedException {
        ExecutorService executorService1 = Executors.newFixedThreadPool(3, new 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);
    }
}ThreadFactory("自定义线程池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);
    }
}

总结

好的线程命名,也许会让后面的你少掉几个头发!

声明:本文由会火号官方原创,如若转载,请注明出处:https://www.huihuohao.com/s/2416.html

发表评论

登录后才能评论