sentinel之自定义线程池工厂的使用

前言:

线程池是我们在开发工作中经常使用到的。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的使用。

Guess you like

Origin blog.csdn.net/qq_26323323/article/details/121273975