写点什么

【连载 15】线程池选择

作者:FunTester
  • 2025-02-06
    河北
  • 本文字数:2955 字

    阅读完需:约 10 分钟

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 开始做性能测试


如果本书内容对你有所帮助,希望各位不吝赞赏,让我可以贴补家用。赞赏两位数可以提前阅读未公开章节。我也会尝试制作本书的视频教程,包括必要的答疑。

发布于: 刚刚阅读数: 3
用户头像

FunTester

关注

公众号:FunTester,800篇原创,欢迎关注 2020-10-20 加入

Fun·BUG挖掘机·性能征服者·头顶锅盖·Tester

评论

发布
暂无评论
【连载 15】线程池选择_FunTester_InfoQ写作社区