Fundamentals 25 min read

12 Real-World Java Concurrency Scenarios Every Developer Should Master

This article explores twelve practical multithreading use cases in Java, ranging from simple scheduled tasks and listeners to Excel imports, remote call aggregation, user context handling, MDC logging, high‑concurrency simulation, Kafka message processing, atomic counters, and delayed jobs, providing code examples and best‑practice tips for each scenario.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
12 Real-World Java Concurrency Scenarios Every Developer Should Master

Preface

Concurrent programming is a crucial skill both in interviews and daily work. Below are twelve business scenarios that demonstrate how to apply multithreading in real projects.

1. Simple Scheduled Task

Using Thread you can implement a basic periodic task. The core code is:

public static void init() {
    new Thread(() -> {
        while (true) {
            try {
                System.out.println("下载文件");
                Thread.sleep(1000 * 60 * 5);
            } catch (Exception e) {
                log.error(e);
            }
        }
    }).start();
}

Remember to wrap the thread as a daemon with setDaemon(true) and handle exceptions to keep the loop alive. This approach is suitable for simple periodic jobs such as downloading files or generating static HTML every few minutes.

Pros: easy to learn, low cost, works well for simple periodic tasks.

Cons: cannot schedule a specific start time, lacks advanced features.

2. Listener

When you need to monitor data changes, for example listening to Canal binlog updates, you can control the listener with a configuration switch. The core code is:

@Service
public class CanalService {
    private volatile boolean running = false;
    private Thread thread;
    @Autowired
    private CanalConnector canalConnector;
    public void handle() {
        while (running) {
            // business logic
        }
    }
    public void start() {
        thread = new Thread(this::handle, "name");
        running = true;
        thread.start();
    }
    public void stop() {
        if (!running) return;
        running = false;
    }
}

The switch is managed by Apollo:

public class CanalConfig {
    @Autowired
    private CanalService canalService;
    @ApolloConfigChangeListener
    public void change(ConfigChangeEvent event) {
        String value = event.getChange("test.canal.enable").getNewValue();
        if (BooleanUtils.toBoolean(value)) {
            canalService.start();
        } else {
            canalService.stop();
        }
    }
}

3. Log Collection

In high‑concurrency scenarios you may need to collect user logs without affecting request latency. A simple producer‑consumer model using a blocking queue works:

@Component
public class LoginLogQueue {
    private static final int QUEUE_MAX_SIZE = 1000;
    private BlockingQueue<LoginLog> queue = new LinkedBlockingQueue<>(QUEUE_MAX_SIZE);
    public boolean push(LoginLog loginLog) { return queue.add(loginLog); }
    public LoginLog poll() {
        try { return queue.take(); } catch (InterruptedException e) { e.printStackTrace(); }
        return null;
    }
}

The producer pushes logs, and a consumer thread consumes them and writes to the database. A thread pool can be used to improve throughput.

4. Excel Import

When importing large Excel files, use parallelStream to process rows concurrently:

supplierList.parallelStream().forEach(x -> importSupplier(x));

This leverages the ForkJoinPool to split the task into smaller subtasks, dramatically reducing import time. Be aware of CPU usage when the data volume is huge.

Tip: Monitor CPU usage when importing massive data sets with multithreading.

5. Aggregated Query Interface

Calling multiple remote services sequentially leads to high latency. Use CompletableFuture to invoke them in parallel:

public UserInfo getUserInfo(Long id) throws InterruptedException, ExecutionException {
    final UserInfo userInfo = new UserInfo();
    CompletableFuture<Void> userFuture = CompletableFuture.supplyAsync(() -> { getRemoteUserAndFill(id, userInfo); return true; }, executor);
    CompletableFuture<Void> bonusFuture = CompletableFuture.supplyAsync(() -> { getRemoteBonusAndFill(id, userInfo); return true; }, executor);
    CompletableFuture<Void> growthFuture = CompletableFuture.supplyAsync(() -> { getRemoteGrowthAndFill(id, userInfo); return true; }, executor);
    CompletableFuture.allOf(userFuture, bonusFuture, growthFuture).join();
    return userInfo;
}

Always use a thread pool to avoid creating too many threads.

6. User Context Retrieval

Store the current user in a TransmittableThreadLocal so that it propagates across thread pools:

@Data
public class CurrentUser {
    private static final TransmittableThreadLocal<CurrentUser> THREAD_LOCAL = new TransmittableThreadLocal<>();
    private String id;
    private String userName;
    // ... other fields
    public static void set(CurrentUser user) { THREAD_LOCAL.set(user); }
    public static CurrentUser getCurrent() { return THREAD_LOCAL.get(); }
}

A Spring MVC interceptor sets the user into this context before handling the request.

7. MDC Parameter Transmission

Use SLF4J's MDC to pass trace IDs through HTTP headers:

public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        MdcUtil.add(UUID.randomUUID().toString());
        chain.doFilter(request, response);
    }
}

public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        request.getHeaders().set("traceId", MdcUtil.get());
        return execution.execute(request, body);
    }
}

The interceptor adds the trace ID to outgoing requests, enabling end‑to‑end tracing.

8. High‑Concurrency Simulation

Use CountDownLatch and a thread pool to simulate thousands of concurrent requests:

public static void concurrenceTest() {
    final AtomicInteger atomicInteger = new AtomicInteger(0);
    final CountDownLatch ready = new CountDownLatch(1000);
    final CountDownLatch done = new CountDownLatch(1000);
    ExecutorService executor = Executors.newFixedThreadPool(10);
    for (int i = 0; i < 1000; i++) {
        executor.submit(() -> {
            try { ready.await(); } catch (InterruptedException e) { e.printStackTrace(); }
            for (int j = 0; j < 1000; j++) atomicInteger.incrementAndGet();
            done.countDown();
        });
        ready.countDown();
    }
    try { done.await(); } catch (InterruptedException e) { e.printStackTrace(); }
    executor.shutdown();
}

9. Kafka Message Processing

When a topic accumulates a large backlog, switch to a multi‑threaded consumer using a custom thread pool:

@Configuration
public class ThreadPoolConfig {
    @Value("${thread.pool.corePoolSize:5}") private int corePoolSize;
    @Value("${thread.pool.maxPoolSize:10}") private int maxPoolSize;
    @Value("${thread.pool.queueCapacity:200}") private int queueCapacity;
    @Value("${thread.pool.keepAliveSeconds:30}") private int keepAliveSeconds;
    @Value("${thread.pool.threadNamePrefix:ASYNC_}") private String threadNamePrefix;
    @Bean("messageExecutor")
    public Executor messageExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix(threadNamePrefix);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

@Service
public class MyConsumerService {
    @Autowired private Executor messageExecutor;
    @KafkaListener(id = "test", topics = {"topic-test"})
    public void listen(String message) {
        System.out.println("收到消息:" + message);
        messageExecutor.submit(new MyWork(message));
    }
}

public class MyWork implements Runnable {
    private String message;
    public MyWork(String message) { this.message = message; }
    @Override public void run() { System.out.println(message); }
}

10. Atomic Counter in Multithreaded Imports

Use AtomicInteger to count successful imports safely:

@Service
public class ImportSupplierService {
    private static final AtomicInteger count = new AtomicInteger(0);
    public int importSupplier(List<SupplierInfo> supplierList) {
        if (CollectionUtils.isEmpty(supplierList)) return 0;
        supplierList.parallelStream().forEach(x -> {
            try {
                importSupplier(x);
                count.incrementAndGet();
            } catch (Exception e) { log.error(e.getMessage(), e); }
        });
        return count.get();
    }
}

11. Delayed Scheduled Task

Use ScheduledExecutorService for delayed or periodic jobs, which avoids the single‑thread limitation of Timer:

public class ScheduleExecutorTest {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        service.scheduleAtFixedRate(() -> System.out.println("doSomething"), 1000, 1000, TimeUnit.MILLISECONDS);
    }
}

This approach supports fixed‑rate and fixed‑delay scheduling with multiple threads.

Conclusion

The twelve examples above illustrate common multithreading patterns in Java projects, from simple timers to complex Kafka consumption, helping developers write efficient, scalable code while avoiding typical concurrency pitfalls.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyspringThreadPoolmultithreadingParallelStream
Su San Talks Tech
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.