前言:
线程池是我们在开发工作中经常使用到的。JDK提供了Executors来快速创建线程池。
本文从那些开源框架中来分析下,他们是怎么更好的创建线程池
1.Executors分析
我们常规使用Executors创建线程池如下:
// 创建一个固定线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
1.1 源码角度释义Executors
public class Executors {
public static ExecutorService newFixedThreadPool(int nThreads) {
// 设置核心参数
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
}
public class ThreadPoolExecutor extends AbstractExecutorService {
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
// 我们主要来讨论下threadFactory的创建
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
// 最终线程池的参数就是如下这些入参
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
}
线程池的核心参数就是以上ThreadPoolExecutor中设置的这些。关于这些参数的含义笔者不再赘述,有很多文章已经说明过了。
我们今天主要关注点放在threadFactory上。
1.2 ThreadFactory
这里默认使用Executors.defaultThreadFactory()。ThreadFactory的主要作用是什么呢?
我们可以在ThreadPoolExecutor.execute()方法中看到
public class ThreadPoolExecutor extends AbstractExecutorService {
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
// 小于核心线程数的时候,则调用addWorker()方法
if (addWorker(command, true))
return;
c = ctl.get();
}
...
}
private boolean addWorker(Runnable firstTask, boolean core) {
...
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 创建Worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
...
if (workerAdded) {
// 执行线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
// 重点在这里
this.thread = getThreadFactory().newThread(this);
}
}
可以看到,Worker中的thread,是通过ThreadFactory.newTread()创建的。
所以我们可以得出一个结论,线程池中的线程都是通过ThreadFactory来创建的。
1.3 DefaultThreadFactory
默认情况下,我们都没有设置ThreadFactory,那么会使用JDK默认的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时,默认线程名称为namePrefix + N
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,那么线程名称都是统一使用 pool- 开头。
但是如果是一个框架级别的应用,我们查看应用栈信息时,所有的线程都以pool- 开头,我们就很难确定到我们所需要的线程。所以,成熟的框架都会自定义ThreadFactory。比如今天要介绍的Sentinel就是这样的。
2.Sentinel中ThreadFactory实现与使用
Sentinel中,创建了ThreadFactory的实现,如下
public class NamedThreadFactory implements ThreadFactory {
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
private final boolean daemon;
public NamedThreadFactory(String namePrefix, boolean daemon) {
this.daemon = daemon;
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
this.namePrefix = namePrefix;
}
public NamedThreadFactory(String namePrefix) {
this(namePrefix, false);
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + "-thread-" + threadNumber.getAndIncrement(), 0);
t.setDaemon(daemon);
return t;
}
}
代码很简单,相对于JDK的DefaultThreadFactory,NamedThreadFactory的构造方法提供了两个参数,允许用户在使用时自定义线程名称前缀和是否守护线程标识。
在使用时也比较方便:
private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1,
new NamedThreadFactory("sentinel-metrics-record-task", true));
这样,在打印线程信息时,可以直接根据线程名称前缀来确定到具体的线程作用,非常方便问题的定位。
3.Netty中关于ThreadFactory实现
Netty中也自定义了ThreadFactory的实现,相比Sentinel中,设置了更多的参数,而且功能更强大了些。
public class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolId = new AtomicInteger();
private final AtomicInteger nextId = new AtomicInteger();
private final String prefix;
private final boolean daemon;
private final int priority;
protected final ThreadGroup threadGroup;
// 提供了一系列的构造方法
public DefaultThreadFactory(Class<?> poolType) {
this(poolType, false, Thread.NORM_PRIORITY);
}
public DefaultThreadFactory(String poolName) {
this(poolName, false, Thread.NORM_PRIORITY);
}
public DefaultThreadFactory(Class<?> poolType, boolean daemon) {
this(poolType, daemon, Thread.NORM_PRIORITY);
}
public DefaultThreadFactory(String poolName, boolean daemon) {
this(poolName, daemon, Thread.NORM_PRIORITY);
}
public DefaultThreadFactory(Class<?> poolType, int priority) {
this(poolType, false, priority);
}
public DefaultThreadFactory(String poolName, int priority) {
this(poolName, false, priority);
}
public DefaultThreadFactory(Class<?> poolType, boolean daemon, int priority) {
this(toPoolName(poolType), daemon, priority);
}
public static String toPoolName(Class<?> poolType) {
ObjectUtil.checkNotNull(poolType, "poolType");
String poolName = StringUtil.simpleClassName(poolType);
switch (poolName.length()) {
case 0:
return "unknown";
case 1:
return poolName.toLowerCase(Locale.US);
default:
if (Character.isUpperCase(poolName.charAt(0)) && Character.isLowerCase(poolName.charAt(1))) {
return Character.toLowerCase(poolName.charAt(0)) + poolName.substring(1);
} else {
return poolName;
}
}
}
// 可以看到,这里不但提供了线程名称前缀和daemon,而且支持用户设置线程级别和threadGroup
public DefaultThreadFactory(String poolName, boolean daemon, int priority, ThreadGroup threadGroup) {
ObjectUtil.checkNotNull(poolName, "poolName");
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
throw new IllegalArgumentException(
"priority: " + priority + " (expected: Thread.MIN_PRIORITY <= priority <= Thread.MAX_PRIORITY)");
}
prefix = poolName + '-' + poolId.incrementAndGet() + '-';
this.daemon = daemon;
this.priority = priority;
this.threadGroup = threadGroup;
}
public DefaultThreadFactory(String poolName, boolean daemon, int priority) {
this(poolName, daemon, priority, System.getSecurityManager() == null ?
Thread.currentThread().getThreadGroup() : System.getSecurityManager().getThreadGroup());
}
@Override
public Thread newThread(Runnable r) {
Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
try {
if (t.isDaemon() != daemon) {
t.setDaemon(daemon);
}
if (t.getPriority() != priority) {
t.setPriority(priority);
}
} catch (Exception ignored) {
}
return t;
}
protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(threadGroup, r, name);
}
}
Netty中ThreadFactory的创建就更优秀了,提供了4个参数供用户选择创建。基本涵盖了我们可以设置的所有参数,完全的支持用户的自定义。
总结:
在常规的业务开发中,我们也可以考虑这种自定义ThreadFactory的方式。当然,拿来主义,直接把Netty的这个DefaultThreadFactory拷贝过来也是可以的。设置好合适的线程名称前缀,方便问题的查询定位。
留下一个小问题,为什么Netty中的这个DefaultThreadFactory.newThread()方法要又包装一层呢?
这里牵涉到ThreadLocal的使用,下篇文章再来介绍下Netty中自定义的FastThreadLocal的使用。