【并发处理】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯

 

代码示例

首先我们看一下如果线程没有命名的话,发生异常的错误日志:

/**
 * @Author: maochenfei
 * @Date:
 * @Description:
 */
public class ThreadNoName {
    public static void main(String[] args) {

        //订单模块
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("保存订单的线程");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                throw new NullPointerException();
            }
        });

        //发货模块
        Thread threadTwo = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("保存收获地址的线程");
            }
        });
        threadOne.start();
        threadTwo.start();
    }
}

如果发生异常:

保存订单的线程
保存收获地址的线程
Exception in thread "Thread-0" java.lang.NullPointerException
	at com.jd.p3c.ThreadNoName$1.run(ThreadNoName.java:21)
	at java.lang.Thread.run(Thread.java:745)

从异常日志中我们可以看到Thred-0抛出了NPE(NullPointerException, 空调格指针异常),那么单看这个日志根本无法判断是订单模块的线程抛出的异常

源码分析

java.lang.Thread.java

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
 
/**
 * Initializes a Thread with the current AccessControlContext.
 *
 * 使用当前的AccessControlContext初始化一个线程。
 * 
 * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean)
 */
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    init(g, target, name, stackSize, null, true);
}
 
/* For autonumbering anonymous threads. */
/* 用于自动编写匿名线程 */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

从Thread.class源码类中,我们可以看出如果调用了没有指定线程名字的方法创建了线程,内部会使用"Thread-"+nextThreadNum()作为线程的默认名字。可知threadInitNumber是static变量,nextThreadNum是static方法,所以线程的编号是全应用唯一的并且是递增的,另外这里由于涉及到了多线程递增threadInitNumber也就是执行读取-递增-写入操作,而这个是线程是不安全的,所以又使用了方法级别的synchronized进行了同步

所以当一个系统中有多个业务模块并且每个模块中都有自己要跑的线程,如果遇到问题除非是抛出与业务相关的异常,否则要是都抛出类似上面的NPE异常,根本没法判断是哪一个模块出现了问题,现在将以上代码修改如下:

public class ThreadWithName {
    
    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() {
            @Override
            public void run() {
                System.out.println("保存订单的线程");
                throw new NullPointerException();
            }
        }, THREAD_SAVE_ORDER);
        
        // 发货模块
        Thread threadTwo = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("保存收货地址的线程");
            }
        }, THREAD_SAVE_ADDR);
 
        threadOne.start();
        threadTwo.start();
    }
}

再次运行,异常信息如下图所示,在创建线程的时候给线程指定了一个与具体业务模块相关的名字,从运行结果就可以定位到是保存订单模块抛出了NPE异常,一下子就可以定位到问题。

[转载]创建线程或线程池时请指定有意义的线程名称,方便出错时回溯的图片-高老四博客 第2张

同理线程池未声明也有问题:

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author: maochenfei
 * @Date: 2020/7/19 10:19
 * @Description:
 */
public class ThreadPoolWithoutName {
    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() {
            @Override
            public void run() {
                System.out.println("接受用户链接线程");
                throw new NullPointerException();
            }
        });

        // 具体处理用户请求模块
        executorTwo.execute(new  Runnable() {
            @Override
            public void run() {
                System.out.println("具体处理业务请求线程");
            }
        });

        executorOne.shutdown();
        executorTwo.shutdown();
    }
}
接受用户链接线程
Exception in thread "pool-1-thread-1" java.lang.NullPointerException
	at com.jd.p3c.ThreadPoolWithoutName$1.run(ThreadPoolWithoutName.java:24)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
具体处理业务请求线程

同理我们并不知道是那个模块的线程池抛出了这个异常,那么我们看下这个pool-1-thread-1是如何来的。其实是使用了线程池默认的ThreadFactory,翻看线程池创建的源码如下(源码来自jdk: java.util.concurrent.ThreadPoolExecutor.class & java.util.concurrent.Executor.class & java.util.concurrent.Executor$DefaultThreadFactory):

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
 
/**
 * The default thread factory
 * 默认的线程工厂
 */
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;
    }
}

如上代码DefaultThreadFactory的实现可知:

  • poolNumber是static的原子变量用来记录当前线程池的编号是应用级别的,所有线程池公用一个,比如创建第一个线程池时候线程池编号为1,创建第二个线程池时候线程池的编号为2,这里pool-1-thread-1里面的pool-1中的1就是这个值。
  • threadNumber是线程池级别的,每个线程池有一个该变量用来记录该线程池中线程的编号,这里pool-1-thread-1里面的thread-1中的1就是这个值。
  • namePrefix是线程池中线程的前缀,默认固定为pool。
  • 具体创建线程,可知线程的名称使用namePrefix + threadNumber.getAndIncrement()拼接的。

从上知道我们只需对实现ThreadFactory并对DefaultThreadFactory的代码中namePrefix的初始化做手脚,当需要创建线程池是传入与业务相关的namePrefix名称就可以了,代码如下:

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * @Author: maochenfei
 * @Date: 2020/7/19 10:23
 * @Description:
 */
public class ThreadPoolWithName {
    static class NamedThreadFactory implements ThreadFactory {
        private static final AtomicInteger POOL_NUMBER = 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 + "-" + POOL_NUMBER.getAndIncrement() + "-thread-";
        }

        @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;
        }
    }

    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"));

    public static void main(String[] args) {

        //接受用户链接模块
        executorOne.execute(new  Runnable() {
            @Override
            public void run() {
                System.out.println("接受用户链接线程");
                throw new NullPointerException();
            }
        });

        //具体处理用户请求模块
        executorTwo.execute(new  Runnable() {
            @Override
            public void run() {
                System.out.println("具体处理业务请求线程");
            }
        });

        executorOne.shutdown();
        executorTwo.shutdown();
    }
}

运行结果如下 

Exception in thread "ASYN-ACCEPT-POOL-1-thread-1" java.lang.NullPointerException
	at com.jd.p3c.ThreadPoolWithName$1.run(ThreadPoolWithName.java:53)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
接受用户链接线程
具体处理业务请求线程

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值