Concurrency & Delay in Java
Run parallel work with threads, Runnable, Callable, ExecutorService, CompletableFuture, and schedule delayed or periodic tasks with ScheduledExecutorService.
Waiting & Delay
1. Wait for N Milliseconds
`Thread.sleep(ms)` pauses the current thread for the given number of milliseconds. It throws `InterruptedException` which must be handled.
2. Run a Function Asynchronously
Use `CompletableFuture.runAsync()` to run code in the background without blocking the main thread.
Parallel Execution
1. Creating and Running Threads
Create threads by passing a `Runnable` lambda. Call `start()` — not `run()` — to run on a new OS thread.
2. ExecutorService — Thread Pool
Creating raw threads for every task is expensive. `ExecutorService` manages a pool of reusable threads. Use `Executors.newFixedThreadPool(n)` for a fixed pool size.
Coordinating Async Operations
1. Wait for Multiple Async Tasks
`CompletableFuture.allOf()` waits for all given futures to complete — equivalent to `Promise.all()` in JavaScript.
2. Wait for At Least One Task
`CompletableFuture.anyOf()` completes as soon as the fastest future completes — equivalent to `Promise.race()` in JavaScript.
3. Chaining Async Operations (thenApply)
`thenApply()` chains transformations on a future result — equivalent to `.then()` in JavaScript Promises.
CompletableFuture Chaining: thenAccept, thenRun & exceptionally
1. thenAccept() — Consuming the Result
`thenAccept(consumer)` runs a callback with the result when the future completes — does not return a new value.
2. thenRun() — Cleanup / Side Effect
`thenRun(runnable)` runs a callback after the future completes but does not receive the result — useful for cleanup.
3. exceptionally() — Handling Errors
`exceptionally(fn)` catches any exception in the chain and returns a fallback value — equivalent to `.catch()` in JavaScript Promises.
4. whenComplete() — then + catch Combined
`whenComplete(biConsumer)` runs after the future completes — either with a result or an exception. Both are passed as arguments.
5. Chaining Style vs get() Blocking
Prefer chaining callbacks over calling `.get()` to avoid blocking the current thread.
Passing Data Between Async & Sync Code
1. Passing Data from an Async Function
`CompletableFuture.supplyAsync()` returns a future carrying the result. Use `join()` or `thenAccept()` to retrieve it.
Timers & Intervals
1. Run After Delay (setTimeout equivalent)
`ScheduledExecutorService.schedule()` runs a task once after a specified delay.
2. Run at Every Interval (setInterval equivalent)
`scheduleAtFixedRate()` runs a task repeatedly at a fixed interval.
Measuring Time
1. Calculating Time Interval
Use `System.currentTimeMillis()` or `System.nanoTime()` to measure elapsed time. Prefer `nanoTime()` for short durations.
Thread Safety & Synchronization
1. Race Condition Problem
When multiple threads modify shared state without coordination, results become unpredictable — this is called a race condition.
2. Fix with AtomicInteger
`AtomicInteger` from `java.util.concurrent.atomic` provides thread-safe integer operations without explicit locking.
3. Fix with synchronized
The `synchronized` keyword locks a method or block so only one thread can execute it at a time.
4. Deadlock
A deadlock occurs when two or more threads are each waiting for a lock held by the other — they block forever. Fix by always acquiring locks in a consistent order.