在开发高并发的Java应用时,线程池是绕不开的话题。JDK自带的Executors工具类虽然用起来方便,但实际项目中我们更倾向于手动管理ThreadPoolExecutor,尤其是在线程创建环节,自定义线程工厂能带来更大的灵活性和可观测性。
为什么需要自定义线程工厂
默认情况下,线程池使用的是ThreadFactory的默认实现,创建出来的线程名字都是类似“pool-1-thread-1”这样毫无辨识度的名称。一旦系统出问题,排查线程堆栈时根本分不清哪个线程属于哪个业务模块。就像快递站里所有快递员都叫“小哥”,客户打电话查件时谁也搞不清到底是谁负责自己的包裹。
通过自定义ThreadFactory,我们可以给线程起有意义的名字,比如“order-service-pool-thread-1”,这样在日志、监控或dump线程时一眼就能定位来源。
动手写一个带命名规则的线程工厂
下面是一个简单的自定义线程工厂实现,它支持前缀命名,并自动编号:
public class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public NamedThreadFactory(String namePrefix) {
this.namePrefix = namePrefix + "-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
if (t.isDaemon()) {
t.setDaemon(false);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
在实际线程池中使用它
接下来,在创建ThreadPoolExecutor时传入这个工厂:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
10,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
new NamedThreadFactory("order-worker"),
new ThreadPoolExecutor.CallerRunsPolicy()
);
这样一来,所有由该线程池创建的线程都会以“order-worker-”开头。当系统出现慢查询或死锁时,通过jstack命令抓取线程快照,能迅速锁定与订单相关的线程行为。
进一步增强:加入线程异常日志记录
还可以在线程工厂中统一设置未捕获异常处理器,避免任务中的异常悄无声息地消失:
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
t.setUncaughtExceptionHandler((thread, ex) ->
System.err.println("Thread " + thread.getName() + " threw exception: " + ex)
);
return t;
}
这种处理方式就像给每个员工配备了工作记录仪,出了问题有据可查,而不是互相推诿。
真实场景:电商大促时的订单处理
想象一下双十一期间,订单服务、库存服务、优惠券服务各自都有独立的线程池。如果全都用默认线程名,一旦CPU飙高,运维人员看线程栈完全分不清哪个线程属于哪个业务。而通过自定义线程工厂,分别命名为“order-pool”、“stock-pool”、“coupon-pool”,问题定位效率直接提升一大截。
别小看这一点点改动,它让系统的可观测性上了一个台阶。代码还是那些代码,但维护起来舒服多了。