In any interview, whether senior or junior, experienced or beginner, you’ll encounter a couple of questions about threads, concurrency, and multithreading. In fact, this built-in support for concurrency is one of Java’s strongest strengths and has helped it achieve popularity among entrepreneurs and programmers alike. Most lucrative Java developer positions require excellent multithreading skills and experience developing, debugging, and tuning high-performance, low-latency applications.
Therefore, it is one of the most sought-after skills in interviews. In a typical Java interview, the interviewer slowly starts with the basic concepts of threads, asking questions such as why Threads are needed, how to create them, which is better—extending Thread or implementing Runnable—and then slowly moves on to the complexities of concurrency, the challenges encountered when developing concurrent applications, the high-level concurrency utilities introduced in JDK 1.5, the principles and design patterns of concurrent applications, and classic multithreading issues. Since simply knowing the basics of multithreading isn’t enough, you must know how to deal with concurrency issues such as deadlock, race conditions, memory inconsistency, and various thread safety issues.
These skills are thoroughly tested, presenting various multithreading and concurrency issues. Many Java developers tend to simply skim the questions before the interview, which is fine, but you should be familiar with them. Also, piling up questions and repeating the same exercises wastes a lot of time, so I’ve created this list.
What is a Thread in Java?
A thread is an independent execution path. Its purpose is to take advantage of the multiple processors available on a machine. By using multiple threads, you can speed up CPU-bound tasks. For example, if one thread takes 100 milliseconds to complete a task, you can use 10 threads to reduce that task to 10 milliseconds. Java provides excellent language-level support for multithreading, and this is one of its strongest advantages.
Difference between Thread and Process in Java?
A thread is a subset of a process; in other words, a single process can contain multiple threads. Two processes execute in different memory spaces, but all threads share the same space. Don’t confuse this with stack memory, which is unique for each thread and is used to store thread-specific data.
How to create a Thread?
At the language level, there are two ways to create a Thread. An object of the java.lang.Thread class represents a Thread, but it requires a task to execute, which is an object implementing the java.lang.Runnable interface. Since the Thread class implements the Runnable interface, you can override the run() method by inheriting your class from Thread or implementing the Runnable interface.
When to use Runnable and when to use Thread?
This is a follow-up to the previous question. As we know, a Thread can be created by inheriting from the Thread class or by implementing the Runnable interface. The question arises: which method is better, and when should you use each? This question is easily answered if you know that Java doesn’t support multiple class inheritance, but it does allow you to implement multiple interfaces. This means that it’s better to implement Runnable if you want to inherit from another class.
What is the difference between start() and run() methods?
This is a trick question from the past, but it’s still good enough to demonstrate a superficial understanding of multithreading in Java. The start() method is used to start a new thread. Although start() internally calls the run() method, this is not the same as simply calling run(). If you call run() like a normal method, it’s called on the same thread, and no new thread is started, which is what happens when you call start().
Differences between Runnable and Callable?
Both interfaces represent tasks that are intended to be executed in separate threads. Runnable has existed since JDK 1.0, while Callable was added in JDK 1.5. The main difference between them is that Callable’s call() method can return values and throw exceptions, which is not possible with Runnable’s run() method. Callable returns a Future object, which can contain the result of a computation.
Differences between CyclicBarrier and CountDownLatch?
While both of these synchronizers allow threads to wait for each other, the main difference between them is that you can’t reuse a CountDownLatch after its counter reaches zero, but you can reuse a CyclicBarrier even after the barrier breaks.
What is the Java Memory Model?
The memory model is a set of rules and guidelines that allow Java programs to behave deterministically across multiple memory, processor, and operating system architectures. This is especially important in the case of multithreading. The memory model provides guarantees that changes made by one thread will be visible to others, one of which is the happens-before relationship. This relationship defines several rules that allow programmers to anticipate and determine the behavior of concurrent programs. For example, happens-before guarantees:
- Every action in a flow happens before every action in that flow that follows in program order; this is also known as the program order rule.
- Unlocking a monitor occurs before each subsequent lock of the same monitor, also known as the Monitor Lock Rule.
- A write to a volatile field occurs before each subsequent read of that field, as per the volatile variable rule.
- A call to Thread.start() on a thread occurs before any other thread notices that the thread has been stopped, either after a successful Thread.join() or if Thread.isAlive() returns false; the Thread.start() rule.
- A thread interruption from another thread occurs before the interrupted thread notices the interruption (either from throwing an InterruptedException or from checking isInterrupted()); the thread interruption rule.
- The end of an object’s constructor occurs before the finalizer for that object runs; the Finalizer rule.
- If A happens before B, and B happens before C, then A happens before C, which means happens-before guarantees transitivity.
What is a volatile variable?
Volatile is a special modifier that can only be applied to attributes. In concurrent Java programs, changes made to attributes by different threads are not visible to others in the absence of a synchronizer. A volatile variable ensures that a write occurs before a subsequent read, as stated in the volatile variable rule in the previous question.
What is thread safety? Is the Vector class thread-safe?
Thread safety is a property of an object or code that guarantees that the code will behave as intended when executed or used by multiple threads. For example, a thread-safe counter will not miss any counts if the same counter instance is used by multiple threads. Collection classes can obviously be divided into two categories: thread-safe and non-thread-safe. Vector is thread-safe and achieves this by synchronizing methods that modify the Vector’s state, while its counterpart, ArrayList, is non-thread-safe.
What is a race condition?
A race condition is the cause of elusive bugs. As the name suggests, a race condition occurs due to a race between multiple threads. If the thread that should execute first loses the race and the second thread executes, the code’s behavior changes, leading to non-deterministic bugs. These are among the most difficult bugs to detect and reproduce due to the random nature of the races between threads. An example of a race condition is random execution.
How to stop the flow?
I’ve always said that Java provides rich APIs for everything, but ironically, it doesn’t provide convenient ways to stop a thread. JDK 1.0 included several control methods, such as stop(), suspend(), and resume(), which were deprecated in future releases due to potential deadlock hazards. Since then, the Java API designers have not attempted to provide a robust, thread-safe, and elegant way to stop threads. Programmers primarily rely on the fact that a thread stops itself as soon as it finishes executing its run() or call() methods. To stop threads manually, programmers take advantage of a volatile boolean variable and check its value at each iteration if the run() method contains loops, or interrupt threads with interrupt() to abruptly cancel tasks.
What happens when an exception occurs in a thread?
This is one of those good trick questions. Simply put, if an exception is uncaught, the thread dies; if an uncaught exception handler is installed, it will receive a callback. Thread.UncaughtExceptionHandler is an interface defined as a nested interface for handlers called when a thread is about to terminate due to an uncaught exception. When a thread is about to terminate due to an uncaught exception, the JVM will check for the presence of an UncaughtExceptionHandler using Thread.getUncaughtExceptionHandler() and call the handler’s uncaughtException() method, passing the thread and the exception as arguments.
How to share data between two threads?
You can share data between threads using a shared object or parallel data structures like BlockingQueue.
What is the difference between notify and notifyAll?
This is another tricky question, as multiple threads can be watching a single monitor. Java API developers provide a method for notifying one or all threads of a monitor state change, but they only provide half the implementation. The notify() method doesn’t implement a way to select a specific thread, so it’s only useful when you know for sure that only one thread is waiting. On the other hand, notifyAll() notifies all threads and allows them to compete for the monitor, which guarantees that at least one thread will move forward.
Why aren’t wait, notify, and notifyAll in the Thread class?
This is a design question that tests the candidate’s understanding of existing systems or whether they’ve ever considered something similar but initially inappropriate. To answer this question, you need to provide several reasons why it’s more convenient to implement these methods in the Object class, and not in the Thread class. The first obvious reason is that Java supports locks at the object level, not at the thread level. Every object has a lock, which is acquired by a thread. If a thread needs to wait for a specific lock, it makes more sense to call wait() on the object rather than on the thread itself. If wait() were declared in the Thread class, it wouldn’t be clear which lock the thread is waiting for. In short, since wait, notify, and notifyAll operate at the lock level, it’s more convenient to declare them in the Object class, because a lock is associated with an object.
What is a ThreadLocal variable?
ThreadLocal variables are a special type of variable available to the Java programmer. Just as there is a state variable for states, there are ThreadLocal variables for threads. This is a good way to achieve thread safety for expensive-to-create objects; for example, you can make SimpleDateFormat thread-safe using ThreadLocal. Because it is an expensive class, it is undesirable to use it in a local scope, which requires separate instances for each call. By providing each thread with its own copy, you kill two birds with one stone. First, you reduce the number of instances of expensive objects by reusing a fixed number of instances, and second, you achieve thread safety without losing synchronization and immutability. Another good example of a thread-local variable is the ThreadLocalRandom class, which reduces the number of instances of expensive-to-create Random objects in a multi-threaded environment.
What is FutureTask?
FutureTask represents a cancelable asynchronous computation in a parallel Java application. This class provides a basic Future implementation, with methods for starting and stopping the computation, querying the computation status, and retrieving results. The result can only be retrieved when the computation is complete; the retrieval method will block if the computation is not yet complete. FutureTask objects can be used to wrap Callable and Runnable objects. Since FutureTask implements Runnable, it can be passed to an Executor for execution.
What is the difference between interrupted and isInterrupted?
The main difference between interrupted() and isInterrupted() is that the former clears the interrupt status, while the latter does not. The interrupt mechanism in Java is implemented using an internal flag known as the interrupt status. Interrupting a thread by calling Thread.interrupt() sets this flag. When the interrupted thread checks the interrupt status by calling the static Thread.interrupted() method, the interrupt status is cleared. The non-static isInterrupted() method, which is used by a thread to check the interrupt status of another thread, does not change the interrupt flag. Conventionally, any method that returns with an InterruptedException clears the interrupt flag. However, there is always the possibility that the flag will be immediately set again if another thread calls interrupt().
Why are the wait and notify methods called in a synchronized block?
The primary reason for calling wait and notify from a synchronized block or method is that the Java API requires it. If you call them outside of a synchronized block, your code will throw an IllegalMonitorStateException. A more subtle reason is to avoid race conditions between calls to wait and notify.
Why should you check the wait state in a loop?
It’s possible for a waiting thread to receive false warnings and false wakeup calls if it doesn’t check the wait state in a loop; it simply exits even if the state isn’t reached. When a waiting thread wakes up, it doesn’t consider that the state it was waiting on might still be valid. It might have been valid in the past, but then changed after calling notify() and before the thread woke up. Therefore, it’s always best to call wait() from within a loop.
What are the differences between synchronized and concurrent collections?
While both synchronized and concurrent collections provide thread-safety, the latter is more scalable. Before Java 1.5, programmers only had synchronized collections, which became a source of contention when multiple threads accessed them simultaneously, making system scalability difficult. Java 5 introduced concurrent collections, such as ConcurrentHashMap, which not only provide thread-safety but also improve scalability using modern techniques such as lock stripping and internal table partitioning.
Differences between Stack and Heap?
Why is this question included in questions about multithreading? Because the stack is a region of memory closely associated with threads. Each thread has its own stack, which stores local variables, method parameters, and the call stack. A variable stored on the stack of one thread is not visible to another. On the other hand, the heap is a shared memory region shared by all threads. Objects, whether local or at any other level, are created on the heap. To improve performance, a thread typically caches values from the heap on its stack, which is where volatile variables come in. Volatile indicates to threads that the variable should be read from main memory.
What is a thread pool?
Creating a thread is expensive in terms of time and resources. Creating a thread while a request is being processed will slow down response times, and the process can only create a limited number of threads. To avoid these problems, a thread pool is created during application startup, and threads are reused to process requests. This thread pool is called a “thread pool,” and the threads in it are called worker threads. Starting with Java 1.5, the Java API provides the Executor framework, which allows you to create various thread pools, such as a single thread pool, which processes only one task at a time; a fixed thread pool, a pool with a fixed number of threads; and a cached thread pool, an expandable pool suitable for applications with many short-lived tasks.
How to solve the Producer-Consumer problem?
Most threading problems you solve in reality fall under the Producer-Consumer pattern, where one thread produces a task and a second consumes it. You need to know how to structure the internal thread interactions to solve this problem. At a low level, you can use the wait and notify methods, and at a high level, you can take advantage of Semaphore or BlockingQueue.
How to avoid deadlock?
A deadlock occurs when one thread waits for another thread to act while the second thread simultaneously waits for the first thread to do the same. This is a very serious problem, causing your program to hang and not perform its intended function. A deadlock occurs when these four conditions are met:
- Mutual exclusion: At least one resource must be non-sharable. Only one process can use the resource at any given time.
- Hold and wait: A process holds at least one resource and requests additional resources that are held by other processes.
- No pre-emption: The operating system does not reassign resources if they are already in use; they must be given up voluntarily by the holding process.
- Cyclic waiting: a process waits for another process to free resources, which in turn waits for the first process to free resources.
The simplest way to avoid deadlock is to avoid waiting in a loop, which can be achieved by acquiring locks in a specific order and releasing them in reverse order.
What is the difference between livelock and deadlock?
A livelock is similar to a deadlock, except that in a livelock, the states of threads or processes involved constantly change depending on each other. A livelock is a special case of resource starvation. A real-world example of a livelock is when two people meet in a narrow hallway and, trying to be polite, each steps aside, and so they endlessly move back and forth.
How to check if a thread holds a lock?
I had no idea it was possible to check whether a thread currently holds a lock until I encountered this question in a phone interview. java.lang.Thread has a method called holdsLock() that returns true if and only if the current thread holds a monitor for a particular object.
How to get a thread dump?
A thread dump allows you to find out what a thread is currently doing. There are several ways to obtain a thread dump, depending on the operating system. In Windows, you can use the Ctrl + Break combination, and in Linux, the kill -3 command. You can also use the jstack utility, which operates on the process ID, which you can find using another utility, jps.
What JVM parameter is used to control the thread stack size?
This is one of the simple parameters:- Xss is used to control the stack size of a thread in Java.
Differences between synchronized and ReentrantLock?
There was a time when the only way to achieve mutual exclusion was through the synchronized keyword, but this had several drawbacks, such as the inability to extend a lock beyond a method or code block, and so on. Java 5 addresses this issue by providing more granular control through the Lock interface. ReentrantLock is a common Lock implementation that provides a Lock with the same basic behavior and semantics as an implicit monitor, achieved through the use of synchronized methods, but with enhanced capabilities.
Given 3 streams T1, T2, and T3, how do you implement the sequence T1, T2, T3?
Sequencing can be achieved in many ways, but you can simply use the join() method to start a thread when another thread finishes. To implement the specified sequence, you need to start the last thread first, and then call the join() method in reverse order, so T3 calls T2.join, and T2 calls T1.join, so T1 finishes first and T3 finishes last.
What does the yield method do?
The yield method is one way to request a thread to yield the processor so another can execute. It’s a static method and only guarantees that the current thread will yield the processor, but it doesn’t decide which thread will execute.
What is the parallelism level of ConcurrentHashMap?
ConcurrentHashMap achieves its scalability and thread safety by partitioning the actual map into sections. This partitioning is achieved using a parallelism layer. This layer is an optional parameter of the ConcurrentHashMap constructor,r and its default value is 16.
What is Semaphore?
A Semaphore is a new type of synchronizer. It’s a counter semaphore. Conceptually, a Semaphore manages a set of permits. Each acquire() blocks, if necessary, until a permit is available, then acquires it. Each release() adds a permit, potentially releasing the blocking acquirer. However, these operations don’t use actual permit objects; the Semaphore simply stores the number of available ones and acts accordingly. Semaphore is used to protect expensive resources that are available in limited quantities, such as a pooled database connection.
What happens if the thread pool queue is already full and you submit a task?
If the thread pool queue is full, the submitted task will be rejected. The ThreadPoolExecutor’s submit() method throws a RejectedExecutionException, after which the RejectedExecutionHandler is called.
What is the difference between the submit() and execute() methods in a thread pool?
Both methods are ways of submitting a task to the thread pool, but there is a slight difference between them. Execute(Runnable command) is defined in the Executor interface and executes the submitted task at a later time, but, more importantly, it returns nothing. On the other hand, submit() is an overloaded method; it can accept Runnable and Callable tasks and can return a Future object, which can be used to cancel execution and/or wait for the result of the computation. This method is defined in the ExecutorService interface, which inherits from the Executor interface, and every thread pool class, such as ThreadPoolExecutor or ScheduledThreadPoolExecutor, inherits these methods.
What is a blocking method?
A blocking method is one that blocks until its task is completed; for example, the accept() method on ServerSocket blocks while waiting for a client to connect. Here, blocking means that control will not return to the calling method until the task is completed. On the other hand, there are asynchronous or non-blocking methods that complete before the task is completed.
Is Swing thread-safe?
Simply put, no, Swing is not thread-safe, but you need to explain what you mean by that, even if the interviewer doesn’t ask. When we say Swing is not thread-safe, we usually mean that it’s a component that can’t be modified by multiple threads. All changes to GUI components must be made on the AWT thread, and Swing provides synchronous and asynchronous methods for scheduling such changes.
Differences between invokeAndWait and invokeLater?
These are two Swing API methods that allow developers to update GUI components from threads other than the Event Dispatch Thread. InvokeAndWait() synchronously updates a GUI component, such as a progress bar; each time progress is reached, the bar should be updated to reflect the changes. If progress is tracked in another thread, it should call invokeAndWait() to schedule the component’s update from the Event Dispatch Thread. invokeLater () is an asynchronous call to update components.
Which Swing API methods are thread-safe?
This question is again about Swing and thread safety. Although Swing components are not thread-safe, there are methods that can be safely called from multiple threads. I know that repaint() and revalidate() are thread-safe, but there are other methods on various Swing components, such as setText() on JTextComponent and the insert() and append() methods on the JTextArea class.
How to create immutable objects?
This question may seem unrelated to multithreading and parallelism, but it is. Immutability helps simplify already complex concurrent code. An immutable object is very expensive for developers, as it can be shared without any synchronization. Unfortunately, Java doesn’t have an @Immutable annotation that will make your object immutable; developers have to work hard to achieve this. To create an immutable object, you need to follow the basics: initialize in the constructor, avoid setters, avoid reference leaks, and store copies of mutable objects separately.
What is ReadWriteLock?
In general, ReadWriteLock is a result of lock parsing techniques for improving the performance of concurrent applications. This interface was added in Java 5. It operates on a pair of associated locks, one for reading and one for writing. The reading lock can be held simultaneously by multiple reading threads until there are no more writers. The writing lock is exclusive. If you prefer, you can implement the interface with your own ruleset, or you can use ReentrantReadWriteLock, which supports a maximum of 65,535 recursive writing locks and 65,535 reading locks.
What is busy spin?
Busy spin is a technique programmers use to force a thread to wait under a certain condition. Unlike traditional methods like wait(), sleep(), or yield(), which involve relinquishing control of the processor, this method doesn’t relinquish control of the processor; instead, it simply executes an empty loop. Why would anyone do this? To preserve the processor cache. On multi-core systems, it’s possible for a suspended thread to resume execution on another core, which requires a cache rebuild. To avoid this costly rebuild, programmers prefer to wait for shorter periods of time, using a busy spin.
What is the difference between volatile and atomic variables?
This is quite an interesting question. At first glance, volatile and atomic variables look very similar, but they are actually different. A volatile variable provides a happens-before guarantee that a write will occur before any subsequent read; it does not guarantee atomicity. For example, the count++ operation won’t be atomic simply because count is declared volatile. On the other hand, the AtomicInteger class provides an atomic method for performing such complex operations atomically. For example, getAndIncrement() is an atomic replacement for the increment operator; it can be used to atomically increment the current value by one. Atomic versions also exist for other data types.
What happens if a thread throws an Exception in a synchronized block?
This is another tricky question for casual Java programmers. No matter how you exit a synchronized block—whether normally, by finishing execution, or abruptly, by throwing an exception—the thread releases the acquired lock upon entering the synchronized block. This is one reason why I prefer the synchronized block lock interface over the interface, which requires special attention when releasing the lock, typically achieved by releasing the lock in a finally block.
What is Singleton’s double-checked locking?
This is one of the most popular interview questions, and yet, despite its popularity, the chances of a candidate answering it are 50% at best. Half the time, they fail when writing the code, and the other half when explaining how it was broken and fixed in Java 1.5. This is an old way of creating a thread-safe singleton that tries to optimize performance by locking only when the singleton instance is first created, but due to its complexity and the fact that it was broken in JDK 1.4, I personally don’t like it. Still, even if you don’t prefer this approach, it’s useful to know for interview purposes.
How to create a thread-safe Singleton?
This question complements the previous one. If you say you don’t like double-checked locking, the interviewer will be forced to ask about alternative ways to create a thread-safe Singleton. And there are alternatives: you can take advantage of class loading and static variable initialization to create a Singleton instance, or you can take advantage of the powerful enumeration type.
List 3 practices you follow in parallel programming?
This is my favorite question because I believe it’s important to follow certain rules when writing concurrent code, which helps with performance, debugging, and maintenance. Here are the top three rules I believe every Java programmer should follow:
- Always give your threads meaningful names. Finding a bug or tracking down an exception in parallel code is quite a challenging task. OrderProcessor, QuoteProcessor, or TradeProcessor are much better than Thread-1, Thread-2, and Thread-3. The name should reflect the task performed by the thread. All major frameworks and even the JDK follow this rule.
- Avoid blocking or reduce the sync scope. Locking is expensive, and context switching is even more expensive. Try to avoid synchronization and locking whenever possible, and you’ll minimize your critical section. Therefore, I prefer a synchronized block over a synchronized method because it gives you absolute control over the scope of locking.
- Between synchronizers and wait and notify, choose synchronizers first. First, synchronizers like CountDownLatch, Semaphore, CyclicBarrier, and Exchanger simplify coding. Implementing complex control flows using wait and notify is very difficult. Second, these classes are written and maintained by the best in the business, and there’s a good chance they’ll be optimized or replaced with better code in future JDK releases. By using high-level synchronization utilities, you automatically gain all these benefits.
- Between Concurrent Collection and Synchronized Collection, choose ConcurrentCollection. Thiss is another simple rule that’s easy to follow and reap the benefits. Concurrent collections are more scalable than their synchronized counterparts, so they’re a better choice when writing concurrent code. So next time you need a map, consider a ConcurrentHashMap before a Hashtable.
How to force a flow to start?
This is a question akin to how to force garbage collection. The short answer is no. You can, of course, request it using System.gc(), but it doesn’t guarantee anything. Java has absolutely no way to force a thread to run; this is controlled by the thread scheduler, and Java doesn’t provide any API for controlling it. This part of Java is still random.
What is a Fork/Join framework?
The Fork/Join framework, introduced in JDK 7, is a powerful utility that allows developers to take advantage of the multiple processors found in modern servers. It’s designed for work that can be recursively broken down into small chunks. The goal is to utilize all available computing power to increase the performance of your application. One significant advantage of this framework is its use of a work-stealing algorithm (from “work” and “steal”). Worker threads that have completed their tasks can “steal” tasks from other threads that are still busy.
What is the difference between calling wait() and sleep() methods?
Although both wait and sleep provide a form of pause in a Java application, they serve different purposes. Wait is used for internal thread communication; it yields the lock if the wait condition is true and waits for notification if the wait condition is false due to another thread’s actions. The sleep() method, on the other hand, simply yields the processor or pauses the current thread for a specified amount of time. Calling sleep() does not release the lock held by the current thread.

