首先我们看一下如果线程没有命名的话,发生异常的错误日志:
/**
* @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异常,一下子就可以定位到问题。
同理线程池未声明也有问题:
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 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的实现可知:
从上知道我们只需对实现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)
接受用户链接线程
具体处理业务请求线程