线程组动态准备测试数据
背景:搜索接口根据不同的型号去算价格
数据:想利用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")
工作原理:
- PostProcessor 在线程组每次迭代结束时,将
_reset标记设为true - PreProcessor 在下一次迭代开始时,检测到
_reset == true,将计数器归零 - 归零后立即将
_reset设为false,防止同一迭代内重复归零
这样,每次线程组重新运行时,计数器都会从 0.0 开始重新累加。
如果你希望更彻底地重置(包括 JMeter 关闭后重新打开也重置),可以在 测试计划 的 setUp 线程组中加一个 JSR223 Sampler,执行 props.remove("globalDecimalCounter"),这样每次运行测试计划前都会清除旧值。
第三个版本其实在执行过程中也有问题,因为代码中加了 synchronized 关键字,导致很多请求的参数都是一样的:

最终实现方案:
提前构建好请求参数放入csv data 中,然后通过CSV Data Set Config 完成压测

本文来自博客园,作者:chuangzhou,转载请注明原文链接:https://www.cnblogs.com/czzz/p/21055806

浙公网安备 33010602011771号