What is a Lock?
We use it when we should block other thread till the current thread doesn't finish its job, other thread should await it.
why would you choose Lock over synchronized?
Lock for big tasks, it's the similar with syncronised but the key difference is that it works with lock() and unlock() methods, inside of try-finally so it finishes even there are some issues
One more advantage of Lock over synchronized — it has tryLock() which lets a thread attempt to acquire the lock without blocking forever. synchronized has no such option.
We use it when we should block other thread till the current thread doesn't finish its job, other thread should await it.
why would you choose Lock over synchronized?
Lock for big tasks, it's the similar with syncronised but the key difference is that it works with lock() and unlock() methods, inside of try-finally so it finishes even there are some issues
lock.lock();
try {
// critical section
} finally {
lock.unlock(); // always releases
}
One more advantage of Lock over synchronized — it has tryLock() which lets a thread attempt to acquire the lock without blocking forever. synchronized has no such option.
What is the keyword and its use cases?
syncronized keywords used when we want to block other threads when the current busy with the client (task) it's like the queue with just one manager, other should await till the manager serve the client completely
Java supports synchronised methods, synchronised blocks
what's the difference between synchronizing on a method vs a block, and which is better?
It really depends on the goal, when we use it on the method level it block the whole method, whenever it has been used inside the block other parts of the methods continue work as usual except the highlihted part
Exactly right! Block-level is more fine-grained — only locks what actually needs protection, leaving the rest of the method free to run. Method-level locks everything unnecessarily.
#synchronized
syncronized keywords used when we want to block other threads when the current busy with the client (task) it's like the queue with just one manager, other should await till the manager serve the client completely
Java supports synchronised methods, synchronised blocks
what's the difference between synchronizing on a method vs a block, and which is better?
It really depends on the goal, when we use it on the method level it block the whole method, whenever it has been used inside the block other parts of the methods continue work as usual except the highlihted part
Exactly right! Block-level is more fine-grained — only locks what actually needs protection, leaving the rest of the method free to run. Method-level locks everything unnecessarily.
// locks entire method — expensive
synchronized void process() {
normalCode(); // doesn't need lock
counter++; // only this needs lock
moreNormalCode(); // doesn't need lock
}
// only locks what matters — better
void process() {
normalCode();
synchronized(this) {
counter++;
}
moreNormalCode();
}
#synchronized
What is volatile?
Volatile keyword helps us to work with up-to-dated data, without it we work with the caced data in l1, l2, l3 when use volatile it comes from the CPU
One follow-up — can volatile replace synchronized for the counter++ example?
no of course, it doesn't work with CAS, it doesn't have atomicity through the operations. Everything it has just to save up-to-dated data from the main memory which goes through CPU
#volatile
Volatile keyword helps us to work with up-to-dated data, without it we work with the caced data in l1, l2, l3 when use volatile it comes from the CPU
One follow-up — can volatile replace synchronized for the counter++ example?
no of course, it doesn't work with CAS, it doesn't have atomicity through the operations. Everything it has just to save up-to-dated data from the main memory which goes through CPU
#volatile
Deadlock in Java
What is it?
When two threads depend on each other's resources — neither can move forward.
Thread A waits for Thread B's resource.
Thread B waits for Thread A's resource.
Both stuck forever. 🔒🔒
How to avoid it?
1. Always acquire locks in the same order
If A and B both lock resource 1 first, then resource 2 — deadlock is impossible.
2. Use tryLock() with a timeout
Thread waits max 3 seconds, then moves on instead of deadlocking.
3. Minimize locks held simultaneously
The fewer locks a thread holds at once, the lower the chance of deadlock.
#deadlock
What is it?
When two threads depend on each other's resources — neither can move forward.
Thread A waits for Thread B's resource.
Thread B waits for Thread A's resource.
Both stuck forever. 🔒🔒
How to avoid it?
1. Always acquire locks in the same order
If A and B both lock resource 1 first, then resource 2 — deadlock is impossible.
2. Use tryLock() with a timeout
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// critical section
} finally {
lock.unlock();
}
} else {
// couldn't acquire — move on instead of waiting forever
}Thread waits max 3 seconds, then moves on instead of deadlocking.
3. Minimize locks held simultaneously
The fewer locks a thread holds at once, the lower the chance of deadlock.
#deadlock
How do Atomics work in action?
It works by logic CAS (compare and swap) even we increment the element (counter++) here 3 operations, read, modify write new element
When we call this method, it wil call the method over and over again till it could change the value.
// Step 1:
read current value
Step 2:
compute next value
int next = current + 1;
Step 3: attempt CAS if
it's a spin loop (optimistic locking). Keep trying until the value hasn't been changed by another thread between read and write.
#atomicity #atomic #CAS
It works by logic CAS (compare and swap) even we increment the element (counter++) here 3 operations, read, modify write new element
When we call this method, it wil call the method over and over again till it could change the value.
// Step 1:
read current value
int current = atomicCounter.get();
Step 2:
compute next value
int next = current + 1;
Step 3: attempt CAS if
atomicCounter.compareAndSet(current, next)) { return; // success }it's a spin loop (optimistic locking). Keep trying until the value hasn't been changed by another thread between read and write.
while(true) {
int current = counter.get();
int next = current + 1;
if (counter.compareAndSet(current, next)) {
break; // success, exit loop
}
// another thread changed it — retry
}#atomicity #atomic #CAS
What is the difference between ConcurrentHashMap and Hashtable?
HashTable is the legacy class, it blocks the entire map till finish its job, it comes to us from the first version of java.
And the next one ConcurrentHashMap works simultaneously when it comes to write/read data to the bucket, it comes from Java8 and works, also it doesn't block anything
#concurrent_hash_map #has_table
HashTable is the legacy class, it blocks the entire map till finish its job, it comes to us from the first version of java.
And the next one ConcurrentHashMap works simultaneously when it comes to write/read data to the bucket, it comes from Java8 and works, also it doesn't block anything
#concurrent_hash_map #has_table
What is an immutable class in Java?
Gotcha! Classes which one creates new element when we try change the value of current element, like String. Every new String element, even when modifying element will be store here.
When it comes to creating our own class, first thing is first, put the keyword final in class level and for all fields as well. And the value to them will given by the constructor.
Gotcha! Classes which one creates new element when we try change the value of current element, like String. Every new String element, even when modifying element will be store here.
When it comes to creating our own class, first thing is first, put the keyword final in class level and for all fields as well. And the value to them will given by the constructor.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class ImmutableClass {
private final int value;
private final List<String> examples;
public ImmutableClass(int value, List<String> examples) {
this.value = value;
this.examples = new ArrayList<>(examples);
}
public int get() {
return value;
}
public List<String> getExamples() {
return Collections.unmodifiableList(examples);
}
}
import java.util.List;
public class Interview {
public static void main(String[] args) throws InterruptedException {
ImmutableClass immutableClass = new ImmutableClass(1, List.of("a", "b", "c"));
int i = immutableClass.get();
System.out.println(i);
System.out.println("immutableClass.getExamples() = " + immutableClass.getExamples());
}
}
What is a Thread Pool and why do we need it?
Thread pool is the place where lives our worker threads, creating new threads every time when we need can be too expensive for us, so we should use thread pools
Oh! newSingleThreadExecutor works with just one thread worker, and tasks will wait till it finishes job.
newFixedThreadPool works with fixedThread pools, it uses all the worker threads to handle with tasks, all the fixedThreads will be used and it will wait till threads will be free.
newScheduledThreadPool works with scheduling tasks, we have 2 options for working with it, scheduleAtFixedRate (if the task couldn't be handle in this fixed time next task starts automatically) and scheduleWithFixedDelay (waits fixed time before starting the next task).
#thread_pool #newSingleThreadExecutor #newFixedThreadPool #newScheduledThreadPool
Thread pool is the place where lives our worker threads, creating new threads every time when we need can be too expensive for us, so we should use thread pools
Oh! newSingleThreadExecutor works with just one thread worker, and tasks will wait till it finishes job.
newFixedThreadPool works with fixedThread pools, it uses all the worker threads to handle with tasks, all the fixedThreads will be used and it will wait till threads will be free.
newScheduledThreadPool works with scheduling tasks, we have 2 options for working with it, scheduleAtFixedRate (if the task couldn't be handle in this fixed time next task starts automatically) and scheduleWithFixedDelay (waits fixed time before starting the next task).
#thread_pool #newSingleThreadExecutor #newFixedThreadPool #newScheduledThreadPool
What is a Work Stealing Pool?
It's a fork join pool, it works according to the logic divide and conquer. The tasks will be divided to small-enough parts. We have 2 classes RecursiveAction (it just calculates, do actions and nothing returns) or RecursiveTask<GenericType> (it returns the result)
The key concept: each worker thread has its own deque (double-ended queue) of tasks. When a thread finishes its own tasks and becomes idle, it steals tasks from the tail of another thread's queue instead of sitting idle. This keeps all threads busy.
Thread 1 queue: [task1, task2, task3, task4]
Thread 2 queue: [empty]
Thread 2 steals task4 from Thread 1's tail
→ Both threads stay busy
Absolutely, but when it comes to large tasks where accuracy is more important than code readability, we should use ForkJoinPool.commonPool() or create a dedicated ForkJoinPool instance.
When you need to configure pool size, handle exceptions properly, or isolate tasks from other parallel streams, creating your own ForkJoinPool instance is the better choice:
#fork_join_pool #work_steal #divide_and_conquer
It's a fork join pool, it works according to the logic divide and conquer. The tasks will be divided to small-enough parts. We have 2 classes RecursiveAction (it just calculates, do actions and nothing returns) or RecursiveTask<GenericType> (it returns the result)
The key concept: each worker thread has its own deque (double-ended queue) of tasks. When a thread finishes its own tasks and becomes idle, it steals tasks from the tail of another thread's queue instead of sitting idle. This keeps all threads busy.
Thread 1 queue: [task1, task2, task3, task4]
Thread 2 queue: [empty]
Thread 2 steals task4 from Thread 1's tail
→ Both threads stay busy
Absolutely, but when it comes to large tasks where accuracy is more important than code readability, we should use ForkJoinPool.commonPool() or create a dedicated ForkJoinPool instance.
When you need to configure pool size, handle exceptions properly, or isolate tasks from other parallel streams, creating your own ForkJoinPool instance is the better choice:
ForkJoinPool customPool = new ForkJoinPool(8);
customPool.submit(() -> list.parallelStream().forEach(...));
#fork_join_pool #work_steal #divide_and_conquer
What is ThreadLocal?
ThreadLocal is already thread safe and faster than synchronised blocks/methods. Every thread uses its own copies in the threads.
We should make sure that we removed the data after using it in our current thread to avoid the memory leak, stale data as well
Like in this example, Axmadullo used in first example, and Sevara in the last
#thread_local #thread_safe
ThreadLocal is already thread safe and faster than synchronised blocks/methods. Every thread uses its own copies in the threads.
ThreadLocal<String> userContext = new ThreadLocal<>();
Thread t1 = new Thread(() -> {
try {
userContext.set("Axmadullo");
System.out.println(userContext.get());
} finally {
userContext.remove();
}
});
Thread t2 = new Thread(() -> {
try {
userContext.set("Sevara");
System.out.println(userContext.get());
} finally {
userContext.remove();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
We should make sure that we removed the data after using it in our current thread to avoid the memory leak, stale data as well
Like in this example, Axmadullo used in first example, and Sevara in the last
#thread_local #thread_safe
How do we handle Callable tasks?
In order to achieve our desired results we need Future<GenericType> class. It works with the type returning by the Callable<GenericType>. But it blocks everytime when we get the element with .get();
#callable #executorservice
In order to achieve our desired results we need Future<GenericType> class. It works with the type returning by the Callable<GenericType>. But it blocks everytime when we get the element with .get();
ExecutorService pool = Executors.newFixedThreadPool(8);
Callable<String> callable = () -> "Callable example";
Future<String> futureResult = pool.submit(callable);
String s = futureResult.get();// blocked here
System.out.println(s);
pool.shutdown();
#callable #executorservice
How do we achieve fully asynchronous programming in Java?
Oh! Java supports CompletableFuture for this reason, it has multiple methods for working with asyncronous tasks like supplyAsync(), runAsync(), thenAccept(), thenApply, thenCombining().
It has several steps without interrupting it can show the result
The difference between
In other words,
Errors in
#completablefuture #asynchronous_calls #
Oh! Java supports CompletableFuture for this reason, it has multiple methods for working with asyncronous tasks like supplyAsync(), runAsync(), thenAccept(), thenApply, thenCombining().
It has several steps without interrupting it can show the result
The difference between
thenApply() and thenCompose() in CompletableFuture is similar to the difference between map and flatMap in Java Streams.thenApply() is used when you want to transform the result of a CompletableFuture into another value. It takes the result and applies a function to it, returning a new CompletableFuture with the transformed value.thenCompose() is used when the next step itself returns another CompletableFuture. It allows you to chain asynchronous operations without creating a nested CompletableFuture<CompletableFuture<T>>.In other words,
thenApply() performs a simple transformation, while thenCompose() is used for asynchronous composition.Errors in
CompletableFuture can be handled using methods such as exceptionally(), handle(), or whenComplete(). For example, exceptionally() allows you to provide a fallback value if an exception occurs.#completablefuture #asynchronous_calls #
thenCompose #thenApplyAdvantages of Spring
What problem does it solve?
In plain Java you manually create and manage all objects and their dependencies — lots of boilerplate code.
Spring does that for you automatically through IoC and DI.
Spring ecosystem modules:
Spring Boot — rapid app setup, no manual configuration
Spring MVC / WebFlux — web applications
Spring Security — authentication & authorization
Spring Cloud — microservices
Spring AI — AI integration
Core benefits:
✅ Eliminates boilerplate object management
✅ Built-in testing support
✅ Easy to build, deploy and scale
✅ Enterprise-ready out of the box
#spring
What problem does it solve?
In plain Java you manually create and manage all objects and their dependencies — lots of boilerplate code.
Spring does that for you automatically through IoC and DI.
Spring ecosystem modules:
Spring Boot — rapid app setup, no manual configuration
Spring MVC / WebFlux — web applications
Spring Security — authentication & authorization
Spring Cloud — microservices
Spring AI — AI integration
Core benefits:
✅ Eliminates boilerplate object management
✅ Built-in testing support
✅ Easy to build, deploy and scale
✅ Enterprise-ready out of the box
#spring
IoC — Inversion of Control
What is it?
The core idea of Spring — giving control of object creation to the Spring container instead of doing it manually.
Before Spring:
With IoC:
Objects managed by the Spring container are called Beans.
The control of object creation is inverted — from the developer to the framework. That's where the name comes from.
#IoC #inversion #bean
What is it?
The core idea of Spring — giving control of object creation to the Spring container instead of doing it manually.
Before Spring:
UserService userService = new UserService(); // you control it
With IoC:
@Autowired
UserService userService; // Spring controls it
Objects managed by the Spring container are called Beans.
The control of object creation is inverted — from the developer to the framework. That's where the name comes from.
#IoC #inversion #bean
DI — Dependency Injection
What is it?
The way Spring creates and injects beans into each other.
3 types:
1. Constructor Injection ✅ most recommended
Errors visible immediately at startup (Fail fast). Dependencies can be final — immutable (make sure they won't be modified).
2. Setter Injection
Used for optional dependencies.
3. Field Injection @Autowired
Least recommended — hides dependencies, makes testing harder. Not just for tests, works everywhere but avoid in production code.
#DI #constructor_injection #setter_injection #field_injection
What is it?
The way Spring creates and injects beans into each other.
3 types:
1. Constructor Injection ✅ most recommended
public UserService(UserRepository repository) {
this.repository = repository;
}Errors visible immediately at startup (Fail fast). Dependencies can be final — immutable (make sure they won't be modified).
2. Setter Injection
public void setRepository(UserRepository repository) {
this.repository = repository;
}Used for optional dependencies.
3. Field Injection @Autowired
@Autowired
UserRepository repository;
Least recommended — hides dependencies, makes testing harder. Not just for tests, works everywhere but avoid in production code.
#DI #constructor_injection #setter_injection #field_injection
What is a Bean?
Objects which created automotically by the spring container and inversioned by it
We can use Annotations like Component, or other specific annotations (Controller, Service, Repository)
Or create beans in configuration class, using @Bean over the methods, and point @Configuration in class level.
Or Use XML way to create beans
#bean #annotation #configuration
Objects which created automotically by the spring container and inversioned by it
We can use Annotations like Component, or other specific annotations (Controller, Service, Repository)
Or create beans in configuration class, using @Bean over the methods, and point @Configuration in class level.
Or Use XML way to create beans
#bean #annotation #configuration
What is the difference between ApplicationContext and BeanFactory?
BeanFactory vs ApplicationContext
BeanFactory — legacy
XML based configuration
Creates beans lazily — only when requested
Basic bean management only
ApplicationContext — modern ✅
Extends BeanFactory — has everything it has + more
Creates beans eagerly — all at startup
Supports AOP
Supports events
Recommended for all modern Spring apps
#bean #spring #application_context #bean_factory
BeanFactory vs ApplicationContext
BeanFactory — legacy
XML based configuration
Creates beans lazily — only when requested
Basic bean management only
ApplicationContext — modern ✅
Extends BeanFactory — has everything it has + more
Creates beans eagerly — all at startup
Supports AOP
Supports events
Recommended for all modern Spring apps
#bean #spring #application_context #bean_factory
@Qualifier What is it?
When multiple implementations of the same interface exist, @Qualifier tells Spring exactly which one to inject.
Without it — Spring throws:
NoUniqueBeanDefinitionException — found 2 candidates, doesn't know which to pick.
Two ways to fix:
#qualifier
When multiple implementations of the same interface exist, @Qualifier tells Spring exactly which one to inject.
@Autowired
@Qualifier("emailNotificationService")
private NotificationService notificationService;
Without it — Spring throws:
NoUniqueBeanDefinitionException — found 2 candidates, doesn't know which to pick.
Two ways to fix:
@Qualifier("beanName") // explicitly pick one
@Primary // mark one implementation as the default choice#qualifier
How many ways exist to create Beans?
@Repository — adds exception translation, converts raw DB exceptions into Spring's DataAccessException. The only one with real functional difference.
@Service — purely semantic, no extra functionality. Just indicates business logic layer.
@Component — generic base annotation. Use when the class doesn't fit @Service or @Repository.
#bean #repository #service #component
@Repository — adds exception translation, converts raw DB exceptions into Spring's DataAccessException. The only one with real functional difference.
@Service — purely semantic, no extra functionality. Just indicates business logic layer.
@Component — generic base annotation. Use when the class doesn't fit @Service or @Repository.
#bean #repository #service #component
What does @ComponentScan do?
It shows the location of beans where compiler should search for beans to inject
#componentScan
It shows the location of beans where compiler should search for beans to inject
#componentScan
How many Bean scopes exist?
singleton (object creates once and uses everywhere) , prototype (new object every time) , session (used rarely)
#singleton #prototype
singleton (object creates once and uses everywhere) , prototype (new object every time) , session (used rarely)
#singleton #prototype