Method‑Based Multithreaded Performance Testing Framework in Java
The article describes a Java performance testing framework that was refactored from a request‑based to a method‑based design, introducing a ThreadBase class and specialized adapters for HTTP requests, database queries, and concurrent execution, and discusses the advantages of code‑based concurrency and Groovy.
Previously I wrote a performance testing framework that only targeted single HTTP interfaces; it lacked adaptation for business interfaces and non‑HTTP interfaces. Recently I needed it at work, so I updated the framework to be method‑based rather than request‑based, allowing a base class and separate adapter classes for different 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;
/**
* Multi‑threaded task base class, can be used independently
*/
public abstract class ThreadBase<T> extends SourceCode implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(ThreadBase.class);
/**
* Number of times the task request is executed
*/
public int times;
/**
* CountDownLatch lock
* <p>
* Will be automatically set in the Concurrent class according to the number of threads
* </p>
*/
CountDownLatch countDownLatch;
/**
* Resource to be accessed
*/
public T t;
public ThreadBase(T t) {
this();
this.t = t;
}
public ThreadBase() {
super();
}
/**
* Groovy cannot directly access t, so this method is provided
* @return
*/
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();
}
}
/**
* Preparation before running the method under test
*/
protected abstract void before();
/**
* The method under test
* @throws Exception
*/
protected abstract void doing() throws Exception;
/**
* Processing after the method under test
*/
protected abstract void after();
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
public void setTimes(int times) {
this.times = times;
}
}Below are several implemented base classes:
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 request multithreaded class
*/
public class RequestThread extends ThreadBase {
static Logger logger = LoggerFactory.getLogger(RequestThread.class);
/**
* Request
*/
public HttpRequestBase request;
/**
* Constructor for single request multi‑threaded multiple‑times task
* @param request The request to be executed
* @param times Number of executions per thread
*/
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();
}
/**
* Execute a request multiple times without logging; logging method uses loglong
* <p>This method only suits repeated execution of a single request; it cannot currently adapt to business‑related request sequences.</p>
* @param request Request
* @throws IOException
*/
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();
}
}Below is the database multithreaded class:
package com.fun.frame.thead;
import com.fun.interfaces.IMySqlBasic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
/**
* Database multithreaded class
*/
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();
}
}Below is the concurrent class:
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);
/**
* Thread task
*/
public ThreadBase thread;
public List<ThreadBase> threads;
public int num;
public static Vector<Long> allTimes = new Vector<>();
ExecutorService executorService;
CountDownLatch countDownLatch;
/**
* @param thread Thread task
* @param num Number of threads
*/
public Concurrent(ThreadBase thread, int num) {
this(num);
this.thread = thread;
}
/**
* @param threads Thread group
*/
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);
}
/**
* Execute multithreaded tasks
*/
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);
}
/**
* Calculate result
* <p>This result is for reference only</p>
* @param name Number of threads
*/
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 Redis implementation class is missing because I have not encountered a need for a separate implementation.
Regarding whether to use code or tools for concurrency, I believe code has the advantage: higher threshold, strong adaptability, close to development, and easier tuning. Performance testing and concurrency are just the beginning; a good start enables performance data analysis and parameter tuning. Therefore, the choice of tool or language is secondary; basic testing needs can be met, though implementation costs differ.
Groovy is a JVM‑based dynamic language with two main advantages. First, its compatibility with Java is excellent—most Groovy files can be renamed to .java and used directly, and vice versa, and most Java libraries are usable from Groovy. This brings a very low learning cost and easy onboarding. Second, compiler and IDE support (e.g., IntelliJ) is good, and Groovy‑based frameworks and tools such as Gradle are smoother than Maven.
Click to read the original article; interested readers are welcome to discuss.
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.
