线程组动态准备测试数据

背景:搜索接口根据不同的型号去算价格
数据:想利用JSR223 前置处理器构建请求的数据

过程:

第一个版本JSR223 PreProcessor:

// 1. 定义全局变量名
String counterName = "globalCounter";

// 2. 尝试从全局属性中获取原子计数器对象
Object obj = props.get(counterName);
java.util.concurrent.atomic.AtomicInteger atomicCounter;

// 3. 双重检查锁定(Double-Checked Locking),保证只初始化一次
if (obj == null) {
    synchronized (props) {
        // 再次检查,防止多个线程同时进入
        obj = props.get(counterName);
        if (obj == null) {
            atomicCounter = new java.util.concurrent.atomic.AtomicInteger(0);
            props.put(counterName, atomicCounter);
        } else {
            atomicCounter = (java.util.concurrent.atomic.AtomicInteger) obj;
        }
    }
} else {
    atomicCounter = (java.util.concurrent.atomic.AtomicInteger) obj;
}

// 4. 线程安全地自增并获取新值
int currentCount = atomicCounter.incrementAndGet();


String model = "CBTA-AH1-MXL019-16-A-V-d3-Z5-J";

model = model + String.valueOf(currentCount)

// 5. 将自增后的值转换为字符串,放入 vars 中供当前请求使用
vars.put("currentCount", String.valueOf(currentCount));
vars.put("model", model);


// 6. (可选) 打印日志查看当前值
log.info("当前全局计数器值为: " + currentCount);

log.info("model: " + model);

存在的问题: 使用vars 存储请求数据,每次都会重置为初始1,因为vars是线程级别的

第二个版本JSR223 PreProcessor:

// 1. 定义全局变量名
String counterName = "globalCounter";

// 2. 尝试从全局属性中获取原子计数器对象
Object obj = props.get(counterName);
java.util.concurrent.atomic.AtomicInteger atomicCounter;

// 3. 双重检查锁定(Double-Checked Locking),保证只初始化一次
if (obj == null) {
    synchronized (props) {
        // 再次检查,防止多个线程同时进入
        obj = props.get(counterName);
        if (obj == null) {
            atomicCounter = new java.util.concurrent.atomic.AtomicInteger(0);
            props.put(counterName, atomicCounter);
        } else {
            atomicCounter = (java.util.concurrent.atomic.AtomicInteger) obj;
        }
    }
} else {
    atomicCounter = (java.util.concurrent.atomic.AtomicInteger) obj;
}

// 4. 线程安全地自增并获取新值
int currentCount = atomicCounter.incrementAndGet();

// 5. 将自增后的值转换为字符串,放入 vars 中供当前请求使用
vars.put("myCounter", String.valueOf(currentCount));

// 6. (可选) 打印日志查看当前值
log.info("当前全局计数器值为: " + currentCount);

问题:使用props存储,结果现象就是每次线程组执行完毕,再次执行时 请求的计时器未重置,这是因为 props(JMeterProperties)的作用域是 JVM 全局级别 的,它的生命周期与 JMeter 进程绑定
只要你不关闭 JMeter 软件,props 中存储的变量就会一直保留。
当线程组执行完毕并再次点击“运行”时,虽然线程重新启动了,但 props 里的 globalDecimalCounter 依然保留着上一次运行结束时的值,所以它不会自动重置为 0.0

第三个版本 在JSR223 PostProcessor 后加一个监听器:

JSR223 PreProcessor:

// 1. 定义全局变量名
String counterName = "globalCounter";

// 2. 尝试从全局属性中获取原子计数器对象
Object obj = props.get(counterName);
java.util.concurrent.atomic.AtomicInteger atomicCounter;

// 3. 双重检查锁定(Double-Checked Locking),保证只初始化一次
if (obj == null) {
    synchronized (props) {
        // 再次检查,防止多个线程同时进入
        obj = props.get(counterName);
        if (obj == null) {
            atomicCounter = new java.util.concurrent.atomic.AtomicInteger(0);
            props.put(counterName, atomicCounter);
        } else {
            atomicCounter = (java.util.concurrent.atomic.AtomicInteger) obj;
        }
    }
} else {
    atomicCounter = (java.util.concurrent.atomic.AtomicInteger) obj;
}

// 4. 线程安全地自增并获取新值
int currentCount = atomicCounter.incrementAndGet();


String model = "CBTA-AH1-MXL019-16-A-V-d3-Z5-J";

model = model + String.valueOf(currentCount)

// 5. 将自增后的值转换为字符串,放入 vars 中供当前请求使用
vars.put("currentCount", String.valueOf(currentCount));
vars.put("model", model);


// 6. (可选) 打印日志查看当前值
log.info("当前全局计数器值为: " + currentCount);

log.info("model: " + model);



import java.util.concurrent.atomic.AtomicReference
import java.math.BigDecimal

String counterName = "globalDecimalCounter"

// 1. 获取或初始化全局原子引用
Object obj = props.get(counterName)
AtomicReference<BigDecimal> atomicDecimal

if (obj == null) {
    synchronized (props) {
        obj = props.get(counterName)
        if (obj == null) {
            atomicDecimal = new AtomicReference<BigDecimal>(new BigDecimal("2.1"))
            props.put(counterName, atomicDecimal)
        } else {
            atomicDecimal = (AtomicReference<BigDecimal>) obj
        }
    }
} else {
    atomicDecimal = (AtomicReference<BigDecimal>) obj
}

// 2. 检查是否需要重置(通过自定义标记判断)
String resetFlag = props.get(counterName + "_reset")
if (resetFlag == null || resetFlag.equals("true")) {
    synchronized (props) {
        atomicDecimal.set(new BigDecimal("2.1"))
        props.put(counterName + "_reset", "false")
    }
}

// 3. 线程安全地累加 0.1
BigDecimal step = new BigDecimal("0.1")
BigDecimal currentCount = atomicDecimal.updateAndGet({ current -> current.add(step) })


String model = "CBTA-AH1-MXL019-16-A-V-d3-Z5-J";
model  = model  + String.valueOf(currentCount);
// 4. 放入当前线程变量
vars.put("myDecimalCounter", currentCount.toPlainString());
vars.put("model", model);

JSR223 PostProcessor :

props.put("globalDecimalCounter_reset", "true")

解决方案:监听线程组生命周期

我们需要让计数器知道“线程组重新开始了”。可以通过实现 LoopIterationListener 接口,在每次线程组迭代开始时重置计数器。

以下是修改后的完整代码(兼容 Groovy 2.4):

import java.util.concurrent.atomic.AtomicReference
import java.math.BigDecimal

String counterName = "globalDecimalCounter"

// 1. 获取或初始化全局原子引用
Object obj = props.get(counterName)
AtomicReference<BigDecimal> atomicDecimal

if (obj == null) {
    synchronized (props) {
        obj = props.get(counterName)
        if (obj == null) {
            atomicDecimal = new AtomicReference<BigDecimal>(new BigDecimal("0.0"))
            props.put(counterName, atomicDecimal)
        } else {
            atomicDecimal = (AtomicReference<BigDecimal>) obj
        }
    }
} else {
    atomicDecimal = (AtomicReference<BigDecimal>) obj
}

// 2. 检查是否需要重置(通过自定义标记判断)
String resetFlag = props.get(counterName + "_reset")
if (resetFlag == null || resetFlag.equals("true")) {
    synchronized (props) {
        atomicDecimal.set(new BigDecimal("0.0"))
        props.put(counterName + "_reset", "false")
    }
}

// 3. 线程安全地累加 0.1
BigDecimal step = new BigDecimal("0.1")
BigDecimal currentCount = atomicDecimal.updateAndGet({ current -> current.add(step) })

// 4. 放入当前线程变量
vars.put("myDecimalCounter", currentCount.toPlainString())

如何触发重置?

线程组 上右键 → 添加后置处理器JSR223 PostProcessor,写入以下代码:

// 线程组每次迭代结束时,标记需要重置
props.put("globalDecimalCounter_reset", "true")

工作原理:

  1. PostProcessor 在线程组每次迭代结束时,将 _reset 标记设为 true
  2. PreProcessor 在下一次迭代开始时,检测到 _reset == true,将计数器归零
  3. 归零后立即将 _reset 设为 false,防止同一迭代内重复归零

这样,每次线程组重新运行时,计数器都会从 0.0 开始重新累加。


如果你希望更彻底地重置(包括 JMeter 关闭后重新打开也重置),可以在 测试计划 的 setUp 线程组中加一个 JSR223 Sampler,执行 props.remove("globalDecimalCounter"),这样每次运行测试计划前都会清除旧值。

第三个版本其实在执行过程中也有问题,因为代码中加了 synchronized 关键字,导致很多请求的参数都是一样的:

image

最终实现方案:

提前构建好请求参数放入csv data 中,然后通过CSV Data Set Config 完成压测

image

posted @ 2026-07-02 20:23  chuangzhou  阅读(4)  评论(0)    收藏  举报