Java Interview Questions
Java Multithreading - Theory Questions
1. What is multithreading and what are its advantages?
Multithreading is a programming concept that allows concurrent execution of multiple threads within a single program. Advantages:
- Improved performance: Better CPU utilization
- Responsiveness: Applications remain responsive during long operations
- Resource sharing: Threads share memory and resources
- Economical: Cheaper than process creation and context switching
- Parallelism: Utilize multi-core processors effectively
2. What is the difference between process and thread?
Process:
- Independent execution unit with separate memory space
- Heavyweight, expensive to create and context switch
- Inter-process communication is complex
- More secure (isolated memory)
- Examples: Different applications running on OS
Thread:
- Lightweight unit within a process
- Shares memory and resources with other threads
- Inter-thread communication is simpler
- Less secure (shared memory)
- Examples: Multiple tabs in a browser
3. What are the different ways to create threads in Java?
1. Extending Thread class:
class MyThread extends Thread {
public void run() { /* thread logic */ }
}
MyThread t = new MyThread(); t.start();
2. Implementing Runnable interface:
class MyRunnable implements Runnable {
public void run() { /* thread logic */ }
}
Thread t = new Thread(new MyRunnable()); t.start();
3. Using Lambda expressions (Java 8+):
Thread t = new Thread(() -> { /* thread logic */ }); t.start();
4. What is the difference between extending Thread vs implementing Runnable?
Extending Thread:
- Simple for basic scenarios
- Cannot extend other classes (Java single inheritance)
- Each thread creates separate object
- Less flexible
Implementing Runnable:
- Can extend other classes
- Same Runnable can be shared by multiple threads
- Better object-oriented design
- More flexible and recommended approach
- Allows thread pooling
5. What is the thread lifecycle and its states?
Thread states in Java:
- NEW: Created but not started
- RUNNABLE: Ready to run or running
- BLOCKED: Waiting for monitor lock
- WAITING: Waiting indefinitely for another thread
- TIMED_WAITING: Waiting for specified time
- TERMINATED: Execution completed
State transitions: NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING) → TERMINATED
6. What is the difference between start() and run() methods?
start():
- Starts a new thread of execution
- Calls run() method in new thread
- Can be called only once per thread
- Returns immediately
run():
- Contains the code to be executed in thread
- Called automatically by start()
- Can be called multiple times directly
- Runs in current thread if called directly
Example: t.start() vs t.run()
7. What is synchronization and why is it needed?
Synchronization is a mechanism that ensures that only one thread can access a shared resource at a time. Needed to prevent:
- Race conditions: Unexpected results due to timing
- Data inconsistency: Corrupted shared data
- Thread interference: Multiple threads modifying same data
Example without synchronization:
class Counter {
private int count = 0;
public void increment() { count++; } // Not thread-safe
}
8. What are the different ways to achieve synchronization?
1. Synchronized methods:
public synchronized void method() { ... }
2. Synchronized blocks:
synchronized(object) { ... }
3. Static synchronization:
public static synchronized void method() { ... }
4. Using Lock interface:
Lock lock = new ReentrantLock(); lock.lock(); try { ... } finally { lock.unlock(); }
5. Using atomic variables:
AtomicInteger count = new AtomicInteger(0);
9. What is the difference between synchronized method and synchronized block?
Synchronized Method:
- Locks on the current object (this) for instance methods
- Locks on the class object for static methods
- Simpler syntax
- Coarser-grained locking
Synchronized Block:
- Can lock on any object
- More flexible and fine-grained control
- Better performance (smaller critical sections)
- Can reduce lock contention
Example:
public void method() {
// non-critical code
synchronized(lockObject) {
// critical section
}
}
10. What is deadlock and how can it be prevented?
Deadlock occurs when two or more threads are blocked forever, each waiting for the other to release a lock. Conditions for deadlock:
- Mutual Exclusion: Resources cannot be shared
- Hold and Wait: Thread holds resource while waiting for another
- No Preemption: Resources cannot be forcibly taken
- Circular Wait: Circular chain of threads waiting for resources
Prevention strategies:
- Avoid nested locks
- Use timeout in lock acquisition
- Lock ordering (always acquire locks in same order)
- Use tryLock() with ReentrantLock
11. What is the volatile keyword and when should it be used?
The volatile keyword ensures:
- Visibility: Changes made by one thread are immediately visible to others
- Ordering: Prevents instruction reordering
- No atomicity: Does not provide atomic operations
Use cases:
- Flags for thread termination
- Single writer, multiple readers scenarios
- When you need visibility guarantees but not atomicity
Example:
class Task implements Runnable {
private volatile boolean running = true;
public void run() { while(running) { ... } }
public void stop() { running = false; }
}
12. What is the wait(), notify(), and notifyAll() methods?
These methods are used for inter-thread communication:
- wait(): Releases lock and waits until notified
- notify(): Wakes up one waiting thread
- notifyAll(): Wakes up all waiting threads
Important rules:
- Must be called from synchronized context
- wait() releases the lock, sleep() does not
- notify() doesn't guarantee which thread wakes up
Example (Producer-Consumer pattern):
synchronized(lock) {
while(condition) lock.wait();
// do work
lock.notifyAll();
}
13. What is the difference between wait() and sleep()?
wait():
- Method of Object class
- Releases the lock
- Called from synchronized context
- Can be woken by notify()/notifyAll()
- Used for inter-thread communication
sleep():
- Method of Thread class
- Does not release the lock
- Can be called from any context
- Wakes up after specified time
- Used for pausing thread execution
14. What are daemon threads and how are they different from user threads?
Daemon Threads:
- Background supporting threads
- JVM doesn't wait for them to complete
- Automatically terminated when all user threads finish
- Examples: Garbage collector, timer threads
- Set using setDaemon(true) before start()
User Threads:
- Foreground threads that do actual work
- JVM waits for them to complete
- Must be explicitly terminated
- Examples: Main thread, worker threads
- Default thread type
15. What is the Executor Framework and what are its advantages?
The Executor Framework provides a higher-level replacement for working with threads directly. Advantages:
- Thread reuse: Reduces thread creation overhead
- Task separation: Separates task submission from execution
- Resource management: Better control over thread resources
- Built-in pooling: Various thread pool implementations
Example:
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> { /* task */ });
executor.shutdown();
16. What are the different types of thread pools in Java?
Common thread pool implementations:
- FixedThreadPool: Fixed number of threads
Executors.newFixedThreadPool(10)
- CachedThreadPool: Creates threads as needed, reuses idle threads
Executors.newCachedThreadPool()
- SingleThreadExecutor: Single worker thread
Executors.newSingleThreadExecutor()
- ScheduledThreadPool: For scheduled task execution
Executors.newScheduledThreadPool(5)
- WorkStealingPool: Uses work-stealing algorithm (Java 8+)
17. What are Callable and Future interfaces?
Callable:
- Similar to Runnable but can return result and throw exceptions
- Contains call() method instead of run()
- Used with ExecutorService
Future:
- Represents result of asynchronous computation
- Provides methods to check if computation is complete, wait for completion, retrieve result
Example:
Callable
ExecutorService executor = Executors.newSingleThreadExecutor();
Future
Integer result = future.get(); // blocks until result available
18. What is the Lock interface and how is it different from synchronized?
Lock interface provides more extensive locking operations than synchronized. Differences:
synchronized:
- Built-in language feature
- Automatic lock release
- No timeout support
- No interruptible locking
Lock interface:
- More flexible and sophisticated
- Explicit lock() and unlock() required
- Supports timeout (tryLock())
- Supports interruptible locking (lockInterruptibly())
- Supports multiple Condition objects
Example: ReentrantLock
19. What are atomic variables and when should they be used?
Atomic variables provide atomic operations without synchronization. Types:
- AtomicInteger, AtomicLong, AtomicBoolean
- AtomicReference
- AtomicIntegerArray, AtomicLongArray
Use cases:
- Counter implementations
- Status flags
- Simple shared variables
Advantages:
- Better performance than synchronization
- No deadlock risk
- Lock-free algorithms
Example:
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // thread-safe
20. What are the common multithreading issues and best practices?
Common issues:
- Race conditions, deadlocks, livelocks
- Memory consistency errors
- Thread starvation
- Resource contention
Best practices:
- Prefer Executor Framework over manual thread management
- Use higher-level concurrency utilities from java.util.concurrent
- Keep synchronized blocks as small as possible
- Use immutable objects where possible
- Document thread safety of classes
- Use thread-safe collections for shared data
- Avoid holding locks during I/O operations
- Use proper exception handling in threads
- Always shutdown ExecutorService