Implementing Asynchronous Multithreaded Tasks in SpringBoot with Singleton and ScheduledExecutorService
This article walks through how to use the Singleton pattern together with SpringBoot's ScheduledExecutorService to build a reusable asynchronous task manager, recreate Ruoyi's login‑log recording logic, and verify the non‑blocking behavior with a concrete test case.
The article first examines Ruoyi's built‑in asynchronous task manager ( AsyncManager) which uses a singleton instance, a fixed delay of 10 ms, and a ScheduledExecutorService obtained from Spring's bean container. The core methods are:
public class AsyncManager {
private static AsyncManager me = new AsyncManager();
private final int OPERATE_DELAY_TIME = 10;
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
private AsyncManager() {}
public static AsyncManager me() { return me; }
public void execute(TimerTask task) {
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
public void shutdown() {
Threads.shutdownAndAwaitTermination(executor);
}
}It also shows the factory ( AsyncFactory) that creates TimerTask objects for logging login information, extracting client IP, user‑agent details, and persisting a SysLogininfor entity.
public static TimerTask recordLogininfor(final String username, final String status, final String message, final Object... args) {
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
return new TimerTask() {
@Override
public void run() {
String address = AddressUtils.getRealAddressByIP(ip);
StringBuilder s = new StringBuilder();
s.append(LogUtils.getBlock(ip))
.append(address)
.append(LogUtils.getBlock(username))
.append(LogUtils.getBlock(status))
.append(LogUtils.getBlock(message));
sys_user_logger.info(s.toString(), args);
String os = userAgent.getOperatingSystem().getName();
String browser = userAgent.getBrowser().getName();
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(ip);
logininfor.setLoginLocation(address);
logininfor.setBrowser(browser);
logininfor.setOs(os);
logininfor.setMsg(message);
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
logininfor.setStatus(Constants.SUCCESS);
} else if (Constants.LOGIN_FAIL.equals(status)) {
logininfor.setStatus(Constants.FAIL);
}
SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
}
};
}To simplify the flow, the article recreates a lightweight version called GlobalAsyncManager. It follows the same singleton approach, defines a constant delay, and provides executorTask and shutdown methods.
public class GlobalAsyncManager {
private static final GlobalAsyncManager instance = new GlobalAsyncManager();
private final int OPERATOR_DELAY_TIME = 10;
private ScheduledExecutorService executorService = SpringUtils.getBean("scheduledExecutorService");
private GlobalAsyncManager() {}
public static GlobalAsyncManager getInstance() { return instance; }
public void executorTask(TimerTask task) {
executorService.schedule(task, OPERATOR_DELAY_TIME, TimeUnit.MILLISECONDS);
}
public void shutdown() {
Threads.shutdownAndAwaitTermination(executorService);
}
}A matching AsyncTaskFactory creates a TimerTask that inserts a log string into a dummy BusStudent table, demonstrating how business logic can be decoupled from the calling thread.
public class AsyncTaskFactory {
public static TimerTask recordLogToData(String logText) {
return new TimerTask() {
@Override
public void run() {
BusStudent busStudent = new BusStudent();
busStudent.setAddress(logText);
SpringUtils.getBean(IBusStudentService.class).insertBusStudent(busStudent);
}
};
}
}The article finishes with a JUnit test that schedules the task, sleeps to simulate work, and prints timestamps before and after execution, confirming that the logging occurs asynchronously.
@Test
public void recordLog() throws InterruptedException {
Thread.sleep(1000);
GlobalAsyncManager.getInstance().executorTask(
AsyncTaskFactory.recordLogToData(LocalDateTime.now() + "日志内容"));
Thread.sleep(3000);
System.out.println(LocalDateTime.now());
}Overall, the guide demonstrates the step‑by‑step construction of a reusable async manager using the Singleton pattern and ScheduledExecutorService, shows how to wrap business logic in timer tasks, and validates the asynchronous behavior with a simple test.
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.
The Dominant Programmer
Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi
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.
