Method‑Based Multithreaded Performance Testing Framework in Java
The article introduces a Java performance testing framework that shifts from request‑centric to method‑centric design, provides a reusable ThreadBase class, and demonstrates concrete implementations for HTTP requests, database queries, and a concurrent executor to collect timing metrics, while also discussing the merits of coding versus tooling and Groovy advantages.
Previously the author built a performance testing framework that only handled single HTTP interfaces; to support business and non‑HTTP interfaces they refactored it to be method‑based, introducing a reusable base class so that specific adapters can be created as needed.
The core of the framework is the ThreadBase<T> abstract class, which implements Runnable, manages execution count, timing, a CountDownLatch, and defines abstract before(), doing(), and after() methods for concrete tasks.
package com.fun.frame.thead;
import com.fun.frame.SourceCode;
import com.fun.frame.excute.Concurrent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import static com.fun.utils.Time.getTimeStamp;
/**
* 多线程任务基类,可单独使用
*/
public abstract class ThreadBase<T> extends SourceCode implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(ThreadBase.class);
/** 任务请求执行次数 */
public int times;
/** 计数锁 */
CountDownLatch countDownLatch;
/** 用于设置访问资源 */
public T t;
public ThreadBase(T t) {
this();
this.t = t;
}
public ThreadBase() {
super();
}
/** groovy无法直接访问t,所以写了这个方法 */
public String getT() {
return t.toString();
}
@Override
public void run() {
try {
before();
List<Long> t = new ArrayList<>();
long ss = getTimeStamp();
for (int i = 0; i < times; i++) {
long s = getTimeStamp();
doing();
long e = getTimeStamp();
t.add(e - s);
}
long ee = getTimeStamp();
logger.info("执行次数:{},总耗时:{}", times, ee - ss);
Concurrent.allTimes.addAll(t);
} catch (Exception e) {
logger.warn("执行任务失败!", e);
} finally {
after();
if (countDownLatch != null)
countDownLatch.countDown();
}
}
protected abstract void before();
protected abstract void doing() throws Exception;
protected abstract void after();
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
public void setTimes(int times) {
this.times = times;
}
}Using this base, the author provides a concrete RequestThread class for HTTP requests, which sets request configuration, starts a custom thread pool, executes the request, and stops the pool after completion.
package com.fun.frame.thead;
import com.fun.httpclient.ClientManage;
import com.fun.httpclient.FanLibrary;
import com.fun.httpclient.GCThread;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* http请求多线程类
*/
public class RequestThread extends ThreadBase {
static Logger logger = LoggerFactory.getLogger(RequestThread.class);
/** 请求 */
public HttpRequestBase request;
/** 单请求多线程多次任务构造方法 */
public RequestThread(HttpRequestBase request, int times) {
this.request = request;
this.times = times;
}
@Override
public void before() {
request.setConfig(FanLibrary.requestConfig);
GCThread.starts();
}
@Override
protected void doing() throws Exception {
getResponse(request);
}
@Override
protected void after() {
GCThread.stop();
}
/**
* 多次执行某个请求,但是不记录日志,记录方法用 loglong
* <p>此方法只适应与单个请求的重复请求,对于有业务联系的请求暂时不能适配</p>
*/
void getResponse(HttpRequestBase request) throws IOException {
CloseableHttpResponse response = ClientManage.httpsClient.execute(request);
String content = FanLibrary.getContent(response);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
logger.warn("响应状态码:{},响应内容:{}", content, response.getStatusLine());
if (response != null) response.close();
}
}A QuerySqlThread class is also supplied for database queries, acquiring a connection before execution, running the SQL, and closing the connection afterward.
package com.fun.frame.thead;
import com.fun.interfaces.IMySqlBasic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
/**
* 数据库多线程类
*/
public class QuerySqlThread extends ThreadBase {
private static Logger logger = LoggerFactory.getLogger(QuerySqlThread.class);
String sql;
IMySqlBasic base;
public QuerySqlThread(IMySqlBasic base, String sql, int times) {
this.times = times;
this.sql = sql;
this.base = base;
}
@Override
public void before() {
base.getConnection();
}
@Override
protected void doing() throws SQLException {
base.excuteQuerySql(sql);
}
@Override
protected void after() {
base.mySqlOver();
}
}The Concurrent utility class creates a fixed‑size thread pool, distributes the ThreadBase tasks, waits for completion with a CountDownLatch, records total execution time, saves individual timings, and computes a simple QPS metric.
package com.fun.frame.excute;
import com.fun.bean.PerformanceResultBean;
import com.fun.frame.Save;
import com.fun.frame.SourceCode;
import com.fun.frame.thead.ThreadBase;
import com.fun.profile.Constant;
import com.fun.utils.Time;
import com.fun.utils.WriteRead;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Concurrent {
private static Logger logger = LoggerFactory.getLogger(Concurrent.class);
/** 线程任务 */
public ThreadBase thread;
public List<ThreadBase> threads;
public int num;
public static Vector<Long> allTimes = new Vector<>();
ExecutorService executorService;
CountDownLatch countDownLatch;
/**
* @param thread 线程任务
* @param num 线程数
*/
public Concurrent(ThreadBase thread, int num) {
this(num);
this.thread = thread;
}
/**
* @param threads 线程组
*/
public Concurrent(List<ThreadBase> threads) {
this(threads.size());
this.threads = threads;
}
public Concurrent(int num) {
this.num = num;
executorService = Executors.newFixedThreadPool(num);
countDownLatch = new CountDownLatch(num);
}
/** 执行多线程任务 */
public PerformanceResultBean start() {
long start = Time.getTimeStamp();
for (int i = 0; i < num; i++) {
ThreadBase thread = getThread(i);
thread.setCountDownLatch(countDownLatch);
executorService.execute(thread);
}
shutdownService(executorService, countDownLatch);
long end = Time.getTimeStamp();
logger.info("总计" + num + "个线程,共用时:" + Time.getTimeDiffer(start, end) + "秒!");
return over();
}
private static void shutdownService(ExecutorService executorService, CountDownLatch countDownLatch) {
try {
countDownLatch.await();
executorService.shutdown();
} catch (InterruptedException e) {
logger.warn("线程池关闭失败!", e);
}
}
private PerformanceResultBean over() {
Save.saveLongList(allTimes, num);
return countQPS(num);
}
ThreadBase getThread(int i) {
if (threads == null) return thread;
return threads.get(i);
}
/**
* 计算结果
* <p>此结果仅供参考</p>
*/
public static PerformanceResultBean countQPS(int name) {
List<String> strings = WriteRead.readTxtFileByLine(Constant.LONG_Path + name + Constant.FILE_TYPE_LOG);
int size = strings.size();
int sum = 0;
for (int i = 0; i < size; i++) {
int time = SourceCode.changeStringToInt(strings.get(i));
sum += time;
}
double v = 1000.0 * size * name / sum;
PerformanceResultBean performanceResultBean = new PerformanceResultBean(name, size, sum / size, v);
performanceResultBean.print();
return performanceResultBean;
}
}The author notes that a Redis implementation is omitted because it has not been needed yet.
They argue that writing code directly for concurrency offers higher flexibility and better alignment with development needs compared to relying on external tools, emphasizing that a solid code foundation enables effective performance analysis and tuning.
Additionally, the author praises Groovy as a JVM‑based dynamic language with excellent Java compatibility, low learning curve, and strong IDE support, making it a convenient choice for such testing frameworks.
Readers are invited to view the original article and discuss further.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
