3.2 线程池选择
线程池作为压测引擎的核心执行器,是构建整个方案的重中之重。第 1 章我们已经讲过了线程池的常见类型以及适用场景,这里不多赘述。因为我们选择的是线程模型,为了更好的管理线程及任务,我们选择自定义线程池。设计线程池参数考虑以下几点:
保障足够线程资源执行测试用例。
保障测试任务提交后快速被执行。
保障线程复用,避免测试过程中频繁创建和销毁线程。
保障不会创建远超需求的线程。
这些条件对应到线程池参数上,可以参考 java.util.concurrent.Executors#newFixedThreadPool(int) 方法的内容:
 public static ExecutorService newFixedThreadPool(int nThreads) {    return new ThreadPoolExecutor(nThreads, nThreads,                                  0L, TimeUnit.MILLISECONDS,                                  new LinkedBlockingQueue<Runnable>());}
       复制代码
 
这里做几点修改:
将线程 keepAliveTime 调整为 60 秒,这样可以提升线程复用机会。
我们增加 threadFactory 参数,方便我们打印日志和排查故障。
workQueue 我们限制最大长度,设置为 1,或者使用 java.util.concurrent.SynchronousQueue 代替 java.util.concurrent.LinkedBlockingQueue。
演示代码如下:
 package org.funtester.performance.books.chapter03.section2;
import java.util.concurrent.*;import java.util.concurrent.atomic.AtomicInteger;
/** * ThreadFactory 演示类 */public class ThreadFactoryDemo {
    public static void main(String[] args) {        ThreadFactory threadFactory = new ThreadFactory() {// 创建一个线程工厂
            AtomicInteger index = new AtomicInteger();// 线程安全的线程编号
            @Override            public Thread newThread(Runnable r) {// 重写创建线程方法                Thread thread = new Thread(r);// 创建线程                thread.setName("线程-" + index.incrementAndGet());// 设置线程名称                return thread;// 返回创建的线程            }        };        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10), threadFactory);// 创建线程池        for (int i = 0; i < 8; i++) {// 向线程池提交8个任务            threadPoolExecutor.execute(new Runnable() {// 提交任务                @Override                public void run() {// 任务执行逻辑                    System.out.println(System.currentTimeMillis() + "  线程池中的线程名称: " + Thread.currentThread().getName());// 输出线程名称                    try {                        Thread.sleep(100);// 模拟任务执行时间                    } catch (InterruptedException e) {                        throw new RuntimeException(e);// 抛出运行时异常                    }                }            });        }        threadPoolExecutor.shutdown();// 关闭线程池    }}
       复制代码
 
控制台输出:
 1699978852559  线程池中的线程名称: 线程-11699978852559  线程池中的线程名称: 线程-21699978852559  线程池中的线程名称: 线程-31699978852663  线程池中的线程名称: 线程-31699978852664  线程池中的线程名称: 线程-21699978852664  线程池中的线程名称: 线程-11699978852768  线程池中的线程名称: 线程-31699978852769  线程池中的线程名称: 线程-2
       复制代码
 
为了让每一个线程的名字都不一样,笔者在 ThreadFactory 实现中增加了 AtomicInteger 对象,用于对创建线程进行线程安全的计数。我们创建了最大线程数为 3 的线程池,然后利用线程工厂设置了每个线程的名字,且具有唯一性。为了展示线程复用效果,增加了 workQueue 的容量,避免提交任务时被拒绝。根据打印信息可以得出实际效果符合预期的结论。
在使用的过程中,如果线程在执行任务遭遇中断情况,会重新创建新的线程补充到线程池中,相应的线程工厂中的计数器就会增加,大于线程池最大线程数。
以上线程池的选择基于线程模型,若是选择 TPS 模型,一般我们会尽量选择最大线程数远大于核心线程数,workQueue 容量非常小或者使用 java.util.concurrent.SynchronousQueue,下面是一个简单的例子。
 package org.funtester.performance.books.chapter03.section2;
import java.util.concurrent.*;import java.util.concurrent.atomic.AtomicInteger;
public class TheadPoolForTpsModel {
    public static void main(String[] args) {        ThreadFactory threadFactory = new ThreadFactory() {// 创建一个线程工厂
            AtomicInteger index = new AtomicInteger();// 线程安全的线程编号
            @Override            public Thread newThread(Runnable r) {// 重写创建线程方法                Thread thread = new Thread(r);// 创建线程                thread.setName("线程-" + index.incrementAndGet());// 设置线程名称                return thread;// 返回创建的线程            }        };        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 200, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), threadFactory);// 创建线程池
        for (int i = 0; i < 8; i++) {// 向线程池提交8个任务            threadPoolExecutor.execute(new Runnable() {// 提交任务                @Override                public void run() {// 任务执行逻辑                    System.out.println(System.currentTimeMillis() + "  线程池中的线程名称: " + Thread.currentThread().getName());// 输出线程名称                    try {                        Thread.sleep(100);// 模拟任务执行时间                    } catch (InterruptedException e) {                        throw new RuntimeException(e);// 抛出运行时异常                    }                }            });        }        threadPoolExecutor.shutdown();// 关闭线程池    }}
       复制代码
 
控制台输出:
 1700012277892  线程池中的线程名称: 线程-11700012277893  线程池中的线程名称: 线程-41700012277892  线程池中的线程名称: 线程-31700012277892  线程池中的线程名称: 线程-21700012277893  线程池中的线程名称: 线程-61700012277893  线程池中的线程名称: 线程-51700012277893  线程池中的线程名称: 线程-71700012277893  线程池中的线程名称: 线程-8
       复制代码
 
可以线程池创建了 8 个线程去执行任务。在实际工作中,最大线程数会比例子中设置的 200 还要大,在预估最大线程数时要悲观一些,尽量取较大的线程数。虽然遭遇线程数达到最大线程数时依旧不够用的时候,可以通过线程池的 API 动态调整线程池配置,但是非常不优雅,而且操作难度比较高,不建议新手使用。
书的名字:从 Java 开始做性能测试 。
如果本书内容对你有所帮助,希望各位不吝赞赏,让我可以贴补家用。赞赏两位数可以提前阅读未公开章节。我也会尝试制作本书的视频教程,包括必要的答疑。
 
评论