A comprehensive demonstration of concurrency patterns in Spring Boot, showcasing best practices for building high-performance, concurrent applications.
- Overview
- Technologies
- Concurrency Patterns
- Getting Started
- API Endpoints
- Testing
- Best Practices
- Performance Considerations
This application demonstrates various concurrency patterns and best practices in Java and Spring Boot, including:
- Asynchronous programming with
@Async - CompletableFuture for composable async operations
- Parallel Streams for data processing
- Thread pools with ExecutorService
- Locks for fine-grained synchronization
- Synchronization primitives (CountDownLatch, CyclicBarrier, Semaphore, Phaser)
- Lock-free programming with Atomic classes
- Producer-Consumer with BlockingQueue
- Fork/Join framework for divide-and-conquer algorithms
- Scheduled tasks with
@Scheduled
- Java 17
- Spring Boot 3.5.7
- Spring Web - REST API
- Spring Data JPA - Database access
- H2 Database - In-memory database
- Lombok - Reduce boilerplate code
- JUnit 5 - Unit testing
- Awaitility - Async testing
- Spring Boot Actuator - Monitoring
Spring's @Async provides simple asynchronous method execution with custom thread pools.
Service: AsyncService
Features:
- Fire-and-forget async methods
- Async methods with return values (CompletableFuture)
- Multiple thread pool configurations (default, I/O, CPU, long-running)
- Proper exception handling
Example:
@Async("ioTaskExecutor")
public CompletableFuture<TaskResult<String>> executeIoTask(ProcessingTask task) {
// I/O bound operation
}Use Cases:
- Non-blocking I/O operations
- Background processing
- Email sending
- External API calls
Advanced asynchronous programming with composable futures.
Service: CompletableFutureService
Features:
- Chaining operations (thenApply, thenCompose)
- Combining multiple futures (thenCombine, allOf, anyOf)
- Exception handling (exceptionally, handle)
- Timeout support
Example:
CompletableFuture.supplyAsync(() -> fetchData())
.thenApply(data -> process(data))
.thenApply(result -> transform(result))
.exceptionally(ex -> handleError(ex));Use Cases:
- Complex async workflows
- Dependent operations
- Parallel data fetching
- Timeout-sensitive operations
Declarative parallel processing of collections.
Service: ParallelStreamsService
Features:
- Automatic parallelization
- Filter, map, reduce operations
- Custom ForkJoinPool parallelism
- Performance comparison with sequential
Example:
List<Integer> results = numbers.parallelStream()
.filter(n -> n > 0)
.map(n -> n * n)
.collect(Collectors.toList());Use Cases:
- CPU-intensive operations on large datasets
- Bulk data transformations
- Statistical computations
Low-level thread pool management.
Service: ExecutorServicePatternService
Features:
- Fixed thread pool
- Cached thread pool
- Scheduled executor
- Work-stealing pool
- invokeAll/invokeAny patterns
Example:
ExecutorService executor = Executors.newFixedThreadPool(10);
Future<Result> future = executor.submit(() -> longRunningTask());
Result result = future.get(5, TimeUnit.SECONDS);Use Cases:
- Fine-grained thread pool control
- Task submission with timeouts
- Batch processing
- Custom scheduling
Explicit locking for synchronization control.
Service: LocksService
Features:
- ReentrantLock
- ReadWriteLock
- tryLock with timeout
- Fair/unfair locks
Example:
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// Critical section
} finally {
lock.unlock();
}Use Cases:
- Complex synchronization logic
- Read-heavy workloads (ReadWriteLock)
- Deadlock avoidance (tryLock)
High-level coordination mechanisms.
Service: SynchronizationService
Features:
- CountDownLatch - One-time countdown coordination
- CyclicBarrier - Reusable barrier for phase coordination
- Semaphore - Resource access control
- Phaser - Dynamic phase coordination
Example:
CountDownLatch latch = new CountDownLatch(5);
// ... submit 5 tasks, each calls latch.countDown()
latch.await(); // Wait for all tasksUse Cases:
- Waiting for multiple tasks to complete
- Phase-based parallel algorithms
- Resource pooling
- Rate limiting
Lock-free thread-safe operations.
Service: AtomicOperationsService
Features:
- AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference
- Compare-and-swap (CAS) operations
- LongAdder for high-contention scenarios
- LongAccumulator for custom accumulation
Example:
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // Thread-safe increment
counter.compareAndSet(expected, newValue); // CASUse Cases:
- Counters and metrics
- Lock-free data structures
- High-contention updates
Thread-safe queues for producer-consumer patterns.
Service: BlockingQueueService
Features:
- ArrayBlockingQueue (bounded)
- LinkedBlockingQueue (unbounded)
- PriorityBlockingQueue
- DelayQueue
- SynchronousQueue
Example:
BlockingQueue<Task> queue = new ArrayBlockingQueue<>(10);
queue.put(task); // Producer
Task task = queue.take(); // Consumer (blocks if empty)Use Cases:
- Task queues
- Message passing
- Work distribution
- Buffering
Recursive parallel algorithms.
Service: ForkJoinService
Features:
- RecursiveTask (returns result)
- RecursiveAction (no result)
- Work-stealing
- Divide-and-conquer
Example:
class SumTask extends RecursiveTask<Long> {
protected Long compute() {
if (small enough) return directSum();
SumTask left = new SumTask(leftHalf);
SumTask right = new SumTask(rightHalf);
left.fork();
return right.compute() + left.join();
}
}Use Cases:
- Parallel sorting
- Recursive algorithms
- Tree/graph processing
- Large array operations
Cron-like task scheduling.
Service: ScheduledTasksService
Features:
- Fixed rate execution
- Fixed delay execution
- Cron expressions
- Exception handling
Example:
@Scheduled(fixedRate = 5000)
public void periodicTask() {
// Runs every 5 seconds
}
@Scheduled(cron = "0 0 * * * *")
public void hourlyTask() {
// Runs every hour
}Use Cases:
- Periodic maintenance
- Batch processing
- Data synchronization
- Health checks
- Java 17 or higher
- Gradle 7+
# Build the project
./gradlew build
# Run the application
./gradlew bootRun
# Run tests
./gradlew testThe application will start on http://localhost:8080
Navigate to http://localhost:8080/h2-console
- JDBC URL:
jdbc:h2:mem:testdb - Username:
sa - Password: (empty)
GET /api/concurrency/health
GET /actuator/health
GET /actuator/metrics
GET /actuator/threaddumpPOST /api/concurrency/async/simple?taskName=MyTask
POST /api/concurrency/async/with-return?taskName=MyTask
POST /api/concurrency/async/io-task
POST /api/concurrency/async/cpu-taskPOST /api/concurrency/completable-future/simple?taskName=MyTask
POST /api/concurrency/completable-future/chained?input=test
POST /api/concurrency/completable-future/combined?task1=A&task2=B
POST /api/concurrency/completable-future/all
POST /api/concurrency/completable-future/anyPOST /api/concurrency/parallel-streams/numbers
POST /api/concurrency/parallel-streams/sum
POST /api/concurrency/parallel-streams/factorials?upTo=10
POST /api/concurrency/parallel-streams/compare?count=1000POST /api/concurrency/locks/reentrant?times=100
POST /api/concurrency/locks/try-lock?timeoutMs=1000
POST /api/concurrency/locks/read-write?readers=5&writers=2
POST /api/concurrency/locks/resetPOST /api/concurrency/sync/countdown-latch?numberOfTasks=5
POST /api/concurrency/sync/cyclic-barrier?numberOfThreads=3&cycles=2
POST /api/concurrency/sync/semaphore?permits=3&requesters=10
POST /api/concurrency/sync/phaser?numberOfParties=4&phases=3POST /api/concurrency/atomic/integer?increments=100
POST /api/concurrency/atomic/cas?expectedValue=10&newValue=20
POST /api/concurrency/atomic/long-adder?increments=1000
POST /api/concurrency/atomic/compare?iterations=1000&threads=10
POST /api/concurrency/atomic/resetPOST /api/concurrency/queue/producer-consumer?producers=2&consumers=3&itemsPerProducer=10
POST /api/concurrency/queue/priority?items=10
POST /api/concurrency/queue/delay?items=5POST /api/concurrency/fork-join/sum
POST /api/concurrency/fork-join/square
POST /api/concurrency/fork-join/sort
POST /api/concurrency/fork-join/maxGET /api/concurrency/scheduled/counts
POST /api/concurrency/scheduled/resetGET /api/concurrency/demo/tasks?count=10The project includes comprehensive tests:
- Unit Tests - Test individual services
- Integration Tests - Test REST controllers
- Async Testing - Using Awaitility
# Run all tests
./gradlew test
# Run specific test class
./gradlew test --tests AsyncServiceTest
# Run tests with coverage
./gradlew test jacocoTestReport- I/O Bound Tasks: Large pool (threads > CPU cores)
- CPU Bound Tasks: Pool size β CPU cores
- Mixed Workload: Separate pools for different task types
Always handle exceptions in async methods:
@Async
public CompletableFuture<Result> asyncMethod() {
try {
return CompletableFuture.completedFuture(result);
} catch (Exception e) {
return CompletableFuture.completedFuture(errorResult);
}
}Always shutdown executors:
executor.shutdown();
executor.awaitTermination(60, TimeUnit.SECONDS);Always use try-finally with locks:
lock.lock();
try {
// Critical section
} finally {
lock.unlock();
}- Acquire locks in consistent order
- Use tryLock with timeout
- Keep critical sections small
- Not suitable for I/O operations
- Overhead for small datasets
- Be aware of shared state
- Use atomics for simple operations
- Use locks for complex operations
- Consider LongAdder for high contention
CPU-bound: threads = cores + 1
I/O-bound: threads = cores * (1 + wait_time/compute_time)
Use parallel streams when:
- Dataset size > 1000 elements
- Operations are CPU-intensive
- No I/O operations involved
Minimize lock contention by:
- Reducing critical section size
- Using ReadWriteLock for read-heavy workloads
- Consider lock-free alternatives (atomics)
This project is for educational purposes.
Spring Boot Concurrency Demo - Best Practices Implementation
Note: This application is designed for learning and demonstration purposes. Always benchmark and test thoroughly for production use.