Java Multithreading for Strong Senior Developers
What are the differences between Thread, Runnable, and Callable in Java?
Thread:
Represents an actual thread of execution.
Extending Thread class allows overriding run(), but limits inheritance flexibility.
Runnable:
Functional interface with run() method, does not return result or throw checked
exceptions.
Preferred when task doesn’t need to return a value.
Callable:
Callable<T> has call() method that returns a value and can throw checked exceptions.
Often used with ExecutorService and Future for result-bearing tasks.
Use Callable when result or exception handling is needed.
How would you manage a large number of concurrent tasks efficiently?
Use thread pools via ExecutorService to manage and reuse threads.
Avoid creating a new thread per task — it's resource-intensive.
Common pool types:
FixedThreadPool — for a stable number of threads.
CachedThreadPool — grows dynamically, suitable for short-lived tasks.
ScheduledThreadPool — for delayed or periodic tasks.
Submission interfaces:
submit(Callable) for result-bearing tasks.
execute(Runnable) for fire-and-forget tasks.
Monitor system resources to tune pool size properly.
What are the risks of using synchronized and how can deadlocks be avoided?
synchronized blocks or methods ensure mutual exclusion but introduce potential risks:
Deadlocks: threads waiting on locks held by each other.
Reduced concurrency and throughput.
Priority inversion or thread starvation in high-contention scenarios.
Strategies to avoid deadlocks:
Always acquire locks in a consistent global order.
Avoid holding multiple locks at once unless necessary.
Use tryLock() with timeout (from ReentrantLock) to detect deadlock potential.
Favor higher-level concurrency utilities when possible (e.g., java.util.concurrent).
How does the volatile keyword work in Java multithreading?
volatile ensures visibility of changes to variables across threads.
When a thread writes to a volatile variable:
The new value is immediately visible to other threads reading that variable.
volatile does not ensure atomicity:
i++ is not atomic even if 'i' is volatile.
Common use cases:
Flags for stopping threads (e.g., volatile boolean running).
Read-heavy single-writer scenarios.
Use Atomic variables or synchronized for compound actions requiring atomicity.
How would you implement a thread-safe counter in Java?
Options include:
Using synchronized blocks or methods around increment logic.
Using java.util.concurrent.atomic.AtomicInteger.
Using LongAdder for high-concurrency scenarios.
AtomicInteger:
Provides lock-free, thread-safe increment/decrement operations.
Ideal for most cases.
LongAdder:
Uses striped counters internally to reduce contention.
Outperforms AtomicInteger under high contention, but has slightly more memory
overhead.