What’s the difference between Concurrency and Parallelism?
- Concurrency: multiple tasks make
progress by interleaving on one or more threads (even on single core).
- Parallelism: tasks execute
simultaneously on multiple cores. In Java, you can write concurrent
code without achieving parallel speedup (e.g., due to lock contention,
single core, blocking I/O).
Ways to create a thread in Java?
- Extend
Thread class
- Implement
Runnable
- Implement
Callable + Future
- Using
Executor framework (ExecutorService)
Best practice: Use Runnable/Callable with
ExecutorService (better separation of concerns).
- start()
→ creates a new thread and calls run() internally
- run()
→ executes like a normal method (no new thread)
Calling run() directly does not create a
new thread.
|
Runnable |
Callable |
|
No return value |
Returns a value |
|
Cannot throw checked exception |
Can throw checked exception |
|
run() method |
call() method |
Synchronization
ensures only one thread accesses shared resources at a time, preventing
data inconsistency.
synchronized
void increment() {
count++;
}
Method-level vs Block-level synchronization?
- Method-level: Locks entire method
- Block-level: Locks specific critical
section (better performance)
synchronized(this)
{
// critical section
}
Every Java
object has an intrinsic lock (monitor).
synchronized uses this lock to control access.
A
situation where two or more threads wait forever for each other’s
resources.
Thread
A → holds Lock1 → waits for Lock2
Thread
B → holds Lock2 → waits for Lock1
Avoid using:
- Lock
ordering
·
Timeout locks (tryLock)
·
Avoid nested locks
·
Use higher-level concurrency utilities
|
synchronized |
ReentrantLock |
|
Implicit locking |
Explicit locking |
|
No fairness |
Supports fairness |
|
No try-lock |
tryLock() available |
|
Auto-release |
Must unlock manually |
- Guarantees
visibility, not atomicity
- Prevents
CPU cache inconsistency
volatile
boolean running = true;
Not
a replacement for synchronization.
Classes
in java.util.concurrent.atomic that provide lock-free thread-safe operations.
Example:
AtomicInteger
count = new AtomicInteger(0);
count.incrementAndGet();
- NEW
- RUNNABLE
- BLOCKED
- WAITING
- TIMED_WAITING
- TERMINATED
Difference
between wait() and sleep()?
|
wait() |
sleep() |
|
Releases lock |
Does NOT release lock |
|
Object method |
Thread method |
|
Needs synchronized block |
No synchronization needed |
- notify()
→ wakes one waiting thread
- notifyAll()
→ wakes all waiting threads
Prefer notifyAll() to avoid thread
starvation.
When a
thread never gets CPU time due to priority or lock monopolization.
What is
livelock?
Threads
are active but cannot make progress (keep responding to each other).
How
does Java ensure thread safety in Spring Boot apps?
- Stateless
services
- Thread-safe
beans
- Proper
use of ExecutorService
- Database-level
locking
- Synchronization
when required
Common
multithreading bugs you faced?
- Race
conditions
- Deadlocks
- Improper
synchronization
- Blocking
calls in thread pools
- Memory
visibility issues
The
Executor Framework (java.util.concurrent) provides a high-level API
for managing threads, separating task submission from task
execution.
Instead of creating threads manually, you
submit tasks to an executor.
Problems
with manual threads:
- Expensive
thread creation
- No
reuse
- Hard
lifecycle management
- Poor
error handling
Executors
provide:
- Thread
pooling
- Task
queuing
- Better
performance
- Graceful
shutdown
- Executor
- ExecutorService
- ScheduledExecutorService
|
Executor |
ExecutorService |
|
Only execute() |
submit(), shutdown(), invokeAll() |
|
Fire-and-forget |
Full lifecycle management |
A
set of reusable worker threads that execute submitted tasks from a queue.
Benefits:
- Reduced
overhead
- Controlled
concurrency
- Better
throughput
|
FixedThreadPool |
CPU-bound tasks |
|
CachedThreadPool |
Short-lived async tasks |
|
SingleThreadExecutor |
Sequential execution |
|
ScheduledThreadPool |
Delayed / periodic tasks |
|
ForkJoinPool |
Divide-and-conquer |
|
execute() |
submit() |
|
No return |
Returns Future |
|
Exceptions uncaught |
Exceptions captured |
Represents
the result of an asynchronous computation.
f.get();
// blocks
- Future.get()
blocks
- CompletableFuture supports non-blocking callbacks and chaining
What is
Callable?
Similar
to Runnable but:
- Returns
a value
- Can throw checked exceptions
What
happens if a task throws an exception?
- execute()
→ exception lost
- submit()
→ captured inside Future
Must
call get() to see it.
- Use
core threads
- Queue
tasks
- Create
extra threads if queue is full
- Reject if max threads reached
What is
ForkJoinPool?
Designed
for divide-and-conquer tasks using work-stealing algorithm.
Used
in:
- Parallel
Streams
- RecursiveTask
/ RecursiveAction
Why CopyOnWriteArrayList?
- Thread-safe
without locking during reads
- Ideal for read-heavy scenarios
What is
CompletableFuture?
CompletableFuture
is a Java concurrency API (Java 8+) that represents the result of an asynchronous,
non-blocking computation and allows you to chain, combine, and react to
tasks without blocking threads.
Think of
it as:
A
Future + callbacks + functional style + composition
Problems
with Future:
- get()
blocks
- No
callbacks
- No
easy task chaining
- Poor
error handling
CompletableFuture
solves all of these.
- A
task runs asynchronously in a thread pool
- It
produces a result (or exception)
- Dependent
stages are triggered automatically
- Threads
are not blocked while waiting
CompletableFuture<String>
future =
CompletableFuture.supplyAsync(() -> {
return "Hello";
});
supplyAsync()
→ runs task asynchronously
Uses
ForkJoinPool.commonPool() by default
get()
blocks only at the end
Execution
Flow (Interview Explanation)
Main
Thread
|
|---- submit async task
|
|---- continues execution
|
|---- worker thread completes
task
|
|---- result is set inside
CompletableFuture
|
runAsync() |
CompletableFuture<Void> |
|
supplyAsync() |
CompletableFuture<T> |
Chaining
– thenApply
Transforms
result.
CompletableFuture<Integer>
future =
CompletableFuture.supplyAsync(() -> 10)
.thenApply(x -> x * 2);
➡
Runs after previous stage completes
Consuming
Result – thenAccept
CompletableFuture<Void>
future =
CompletableFuture.supplyAsync(() -> "Data")
.thenAccept(data ->
System.out.println(data));
➡
No return value
Running
Without Input – thenRun
future.thenRun(()
-> System.out.println("Done"));
Async
vs Non-Async Variants (Very Tricky)
thenApply() // runs in same thread
thenApplyAsync() // runs in another thread pool
“Non-Async
continues in the same thread that completed the previous stage.”
No comments:
Post a Comment