Build a Self‑Sustaining Multithreaded Java Task with Graceful Shutdown
This article demonstrates how to create a perpetual multithreaded Java task using a custom thread‑pool utility, supports concurrent execution of multiple long‑running jobs, and implements an elegant shutdown that drains remaining work before terminating.
Hello, I am Su San! Today I will show you how to build a perpetual multithreaded Java task, extracting the multithreading logic from a company‑internal asynchronous task project.
The example is ideal for developers with some work experience who want to learn advanced concurrency techniques or directly apply them in a project.
1. Feature Description
A perpetual asynchronous task runs continuously after it starts. For example, a push‑notification task must keep consuming un‑pushed messages from a database as long as new messages arrive.
Requirements:
Execute multiple perpetual asynchronous tasks simultaneously.
Each task can use several threads to process its data.
Support graceful shutdown: after a shutdown signal, finish processing all pending data before terminating.
Key implementation points:
Each perpetual task runs in its own thread.
Sub‑tasks are processed concurrently using a thread pool.
Shutdown must notify sub‑task threads and allow both the perpetual task and its sub‑tasks to close gracefully.
2. Multithreaded Task Example
2.1 Thread Pool Utility
The TaskProcessUtil class provides a simple thread‑pool manager.
public class TaskProcessUtil {<br/> // Each task has its own thread pool<br/> private static Map<String, ExecutorService> executors = new ConcurrentHashMap<>();<br/><br/> // Initialize a thread pool<br/> private static ExecutorService init(String poolName, int poolSize) {<br/> return new ThreadPoolExecutor(poolSize, poolSize, 0L, TimeUnit.MILLISECONDS,<br/> new LinkedBlockingQueue<Runnable>(),<br/> new ThreadFactoryBuilder().setNameFormat("Pool-" + poolName).setDaemon(false).build(),<br/> new ThreadPoolExecutor.CallerRunsPolicy());<br/> }<br/><br/> // Get or create a thread pool<br/> public static ExecutorService getOrInitExecutors(String poolName, int poolSize) {<br/> ExecutorService executorService = executors.get(poolName);<br/> if (executorService == null) {<br/> synchronized (TaskProcessUtil.class) {<br/> executorService = executors.get(poolName);<br/> if (executorService == null) {<br/> executorService = init(poolName, poolSize);<br/> executors.put(poolName, executorService);<br/> }<br/> }<br/> }<br/> return executorService;<br/> }<br/><br/> // Release thread‑pool resources<br/> public static void releaseExecutors(String poolName) {<br/> ExecutorService executorService = executors.remove(poolName);<br/> if (executorService != null) {<br/> executorService.shutdown();<br/> }<br/> }<br/>}This utility handles pool creation, retrieval, and cleanup.
2.2 Single Task Implementation
The Cat class represents the data to be processed.
@Data<br/>@Service<br/>public class Cat {<br/> private String catName;<br/> public Cat setCatName(String name) {<br/> this.catName = name;<br/> return this;<br/> }<br/>}The ChildTask class encapsulates the perpetual task logic.
public class ChildTask {<br/> private final int POOL_SIZE = 3; // thread‑pool size<br/> private final int SPLIT_SIZE = 4; // data split size<br/> private String taskName;<br/> protected volatile boolean terminal = false; // shutdown flag<br/><br/> public ChildTask(String taskName) { this.taskName = taskName; }<br/><br/> // Entry point<br/> public void doExecute() {<br/> int i = 0;<br/> while (true) {<br/> System.out.println(taskName + ":Cycle-" + i + "-Begin");<br/> List<Cat> datas = queryData();<br/> taskExecute(datas);<br/> System.out.println(taskName + ":Cycle-" + i + "-End");<br/> if (terminal) { break; }<br/> i++;<br/> }<br/> TaskProcessUtil.releaseExecutors(taskName);<br/> }<br/><br/> // Graceful shutdown<br/> public void terminal() {<br/> terminal = true;<br/> System.out.println(taskName + " shut down");<br/> }<br/><br/> private void doProcessData(List<Cat> datas, CountDownLatch latch) {<br/> try {<br/> for (Cat cat : datas) {<br/> System.out.println(taskName + ":" + cat.toString() + ",ThreadName:" + Thread.currentThread().getName());<br/> Thread.sleep(1000L);<br/> }<br/> } catch (Exception e) {<br/> System.out.println(e.getStackTrace());<br/> } finally {<br/> if (latch != null) { latch.countDown(); }<br/> }<br/> }<br/><br/> private void taskExecute(List<Cat> sourceDatas) {<br/> if (CollectionUtils.isEmpty(sourceDatas)) { return; }<br/> List<List<Cat>> splitDatas = Lists.partition(sourceDatas, SPLIT_SIZE);<br/> final CountDownLatch latch = new CountDownLatch(splitDatas.size());<br/> for (List<Cat> datas : splitDatas) {<br/> ExecutorService executorService = TaskProcessUtil.getOrInitExecutors(taskName, POOL_SIZE);<br/> executorService.submit(() -> doProcessData(datas, latch));<br/> }<br/> try { latch.await(); } catch (Exception e) { System.out.println(e.getStackTrace()); }<br/> }<br/><br/> private List<Cat> queryData() {<br/> List<Cat> datas = new ArrayList<>();<br/> for (int i = 0; i < 5; i++) {<br/> datas.add(new Cat().setCatName("LuoXiaoHei" + i));<br/> }<br/> return datas;<br/> }<br/>}2.3 Task Orchestration
public class LoopTask {<br/> private List<ChildTask> childTasks;<br/><br/> public void initLoopTask() {<br/> childTasks = new ArrayList<>();<br/> childTasks.add(new ChildTask("childTask1"));<br/> childTasks.add(new ChildTask("childTask2"));<br/> for (ChildTask childTask : childTasks) {<br/> new Thread(childTask::doExecute).start();<br/> }<br/> }<br/><br/> public void shutdownLoopTask() {<br/> if (!CollectionUtils.isEmpty(childTasks)) {<br/> for (ChildTask childTask : childTasks) { childTask.terminal(); }<br/> }<br/> }<br/><br/> public static void main(String[] args) throws Exception {<br/> LoopTask loopTask = new LoopTask();<br/> loopTask.initLoopTask();<br/> Thread.sleep(5000L);<br/> loopTask.shutdownLoopTask();<br/> }<br/>}2.4 Result Analysis
The console output shows each child task processing five Cat objects per cycle, followed by graceful shutdown messages after the shutdown signal is issued. The second cycle completes remaining work before exiting, confirming the graceful‑shutdown behavior.
2.5 Source Code
GitHub repository: https://github.com/lml200701158/java-study/tree/master/src/main/java/com/java/parallel/pool/ofc
3. Final Thoughts
This classic thread‑pool example was originally written by a colleague (code‑name “Yi Hui”), whose technical level matches an Alibaba P7 engineer. The implementation is elegant and covers many concurrency concepts, making it a valuable learning resource.
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.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
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.
