项目中使用ThreadPoolExecutor进行多线程开发。使用起来很方便,但是当用jstack查看堆栈信息或者Jprofiler调试性能的时候,看到的线程都是pool-1-thread-1\2\3\4之类的。如果一个系统中用到了多个线程池,就无法区分哪个线程造成的系统问题。所以每次都需要点 Thread Dumps 去查看线程执行的具体的代码与堆栈信息,推测是哪个地方出的问题。
于是,需要修改默认的线程名字。查看Executor框架设计,ThreadPoolExecutor类的构造函数,可以实例化和配置灵活多样的线程池服务,该类基本的构造函数中包含以下7个参数:(int corePoolSize,int maximumPoolSize,Long KeepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectExecutionHandler handler)
1、线程数量的控制---线程池模式:
线程池实例的创建主要关心线程池的核心线程数和最大线程数的大小,通过对这两个参数的设置,可以调整线程池的线程模式,如若核心线程池等于最大线程池,则该线程池即为固定大小线程池,若最大线程池无限大,则为无界线程池等。 以上两个参数可以通过setCorePoolSize(int)和setMaximumPoolSize(int)动态更改。 Executors类提供了一般典型线程池的静态实例化方法,可以用于实例化多种类型的线程池,包括固定长度线程池(newFixThreadPool),可缓存线程池(newCachedThreadPool)等。
2、新线程的创建---线程工厂
通过实现线程工厂接口自定义线程工厂,可以定制新线程的名称,所属线程组,优先级,守护状态等信息,一般来说,自定义的线程工厂通常用来设置新线程的名称,向日志中写入信息,维护统计信息等,一个典型的自定义线程工厂应该包含一个定制的Thread子类(参考java并发编程实战P146,程序清单8-7)。 Executors类提供了默认的线程池工厂defaultThreadFactory() 。
3、线程等待机制---任务队列
任务队列用于管理等待执行的任务,队列的选择需要结合线程池机制的选择,包括无界队列,有界队列和同步移交队列,Executors中实现的默认固定长度和单例线程池都默认使用了无界队列。 有界队列包括ArrayBlockQueue,linkedBlockingQueue和PriorityBlockingQueue,有界队列可以避免资源耗尽情况的发生。如果线程池无界,可以使用同步移交队列。
4、线程任务饱和策略---拒绝机制
ThreadPoolExecutor的拒绝机制可以通过setRejectedExecutionHandler()方法动态修改,ThreadPoolExecutor提供了4中预定义的拒绝策略: 1)AbortPolicy:放弃任务执行,并抛出RejectedException异常,通过捕获该异常,可以执行相应的处理方法; 2)CallerRunPolicy:调用者运行,该机制会将任务会退给调用者,从而降低新任务的流量。他会在调用了execute的线程中执行任务,从而阻止主线程向线程池提交新的任务,该机制通常适用Socket服务处理,但对Servlet服务并不适用,因为Servlet本身即为多线程机制,主线程的阻塞无法达到减缓请求速度的效果; 3)DiscardPolicy:抛弃任务; 4)DiscardOldPolicy:抛弃最旧的任务,即抛弃下一个将被执行的任务。 5)还可以实现RejecetExecutionHandler接口,自定义任务的拒绝方法。
ThreadPoolExecutor在实例化之后依然可以使用setter方法修改相关的参数,但可以使用unconfigurableExecutorService工厂方法封装ThreadPoolExecutor使之不可编辑,可以参考Executors中单例线程池的实现。 ThreadPoolExecutor提供了几个可以被子类覆写的方法,包括beforeExecutor,afterExecutor和terminated,使用这些方法可以为线程池添加计时,日志,监视和统计等信息收集功能,而且无论线程是否抛出异常,afterExecutor都将被调用。
根据上面可以知道,Executors.defaultThreadFactory()是缺省的创建线程的实现。看下源码,
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;
}
}
之所以在工具中看到所有的线程都是以 pool- 开头的,那是因为人家就是写死的。如果要修改线程名字,那么我们自定义线程工厂
public static ThreadPoolExecutor executPool = new ThreadPoolExecutor(50, 200, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(5000),
new ThreadFactory(){ public Thread newThread(Runnable r) {
return new Thread(r, "t_pl_pool_" + r.hashCode());
}},
new ThreadPoolExecutor.DiscardOldestPolicy());
还有一种方法,就是线程在start后才会执行run方法,run方法的执行表示这个task真正被线程运行了,这时线程的名称也就确定了。所以可以在run的第一句加上
public class TestCallabble implements Callable<String> {
...
@Override
public String call() throws Exception {
Thread.currentThread().setName(Constants.THREAD_NAME_MUTIL_FEATURE);
return featureBuilder.generateFeature(request, finfoModel, userFeature);
}
}