为什么我们创建线程或者线程池的时候需要指定有意义的线程名称?
最终目的是为了方便回溯。
我们在日常开发中,一个项目中会创建很多个线程池用来资源隔离,但是如果我们没有一个好的命名的话,出问题的时候就会难以定位。
public class Demo5 { public static void main( String[] args ) throws IOException { new Thread(()->{ System.out.println("保存用户信息.........."); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } throw new NullPointerException(); }).start(); System.in.read(); }}上面代码 是启动一个线程来保存用户信息,然后抛出异常!报错信息如下:

从运行错误可以分析,Thread-0 抛出了空指针,那么单从这个日志根本无法判断用户模块线程抛出的异常。我们先分析一下Thread-0是怎么来的。我们先看下创建线程的代码:
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private static int threadInitNumber; private static synchronized int nextThreadNum() { return threadInitNumber++; }从上面代码可知,如果我们没有指定线程名称,内部会自动为我们创建线程名称"Thread-"+nextThreadNum()作为线程的默认名。
如果一个系统中有多个业务模块,如用户模块、订单模块、购物车模块等都使用自己的线程池,而没有指定名称,抛出的异常除非与业务内容有关,否则,根本无法判断是哪一个模块出了问题。
创建线程池时候也需要指定线程池的名称
我们可以使用threadFactory来指定线程的名称:
(1) 可以通过线程工厂给每个创建出来的线程设置更有意义的名字。线程池的命名时通过给这个factory增加组前缀来实现的。在虚拟机栈分析时,就可以知道线程任务由哪个线程工厂产生的。
(2) 使用Guava设置线程名字
new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();
(3)自定义实现:ThreadFactory
Executors静态工厂里默认的threadFactory,线程的命名规则是“pool-数字-thread-数字”。
我们看下线程池:
public class Demo6 { public static void main( String[] args ) throws IOException { ThreadPoolExecutor executorOne = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>()); executorOne.execute(()->{ System.out.println("保存用户信息"); throw new NullPointerException(); }); System.in.read(); }}结果:

我们看到的打印线程池名称是 pool-1-thread-1,那么怎么来的呢?那我们看下ThreadPoolExecutor源码
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); } public static ThreadFactory defaultThreadFactory() { return new DefaultThreadFactory(); } DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } private static final AtomicInteger poolNumber = new AtomicInteger(1);那我们怎样定义ThreadFactory呢?
public class NamedThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final AtomicInteger threadNumber = new AtomicInteger(1); private String namePrefix; private final ThreadGroup group; public NamedThreadFactory( String name ) { this.namePrefix = namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-"; SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); } @Override 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; }}public class Demo6 { public static void main( String[] args ) throws IOException { ThreadPoolExecutor executorOne = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(),new NamedThreadFactory("UserModule")); executorOne.execute(()->{ System.out.println("保存用户信息"); throw new NullPointerException(); }); System.in.read(); }}结果:

我们看到,从错误栈中的线程名称,我们就可以定位到是哪个模块出现了错误。
总结
创建线程或线程池时请 指定有意义的线程名称,方便出错时回溯。
自定义线程工厂,并且根据外部特征进行分组,比如,来自同一机房的调用,把机房编号赋值给

博客强调创建线程或线程池时指定有意义名称的重要性。日常开发中项目会创建多个线程池,若命名不佳,出问题难以定位。可通过线程工厂指定线程名称,如使用Guava或自定义实现,这样出错时能根据线程名回溯到问题模块。

被折叠的 条评论
为什么被折叠?



