feat : implement BankAccount methods + Solutions.md file (including bonus question)
This commit is contained in:
+148
@@ -0,0 +1,148 @@
|
||||
1 - What are atomic variables?
|
||||
|
||||
Atomic variables are variables that support thread-safe operations without using explicit locks.
|
||||
|
||||
They ensure that operations such as incrementing, decrementing, or updating a value are performed as a single indivisible action.
|
||||
|
||||
Difference from ordinary variables:
|
||||
|
||||
Ordinary variable: Multiple threads can modify it simultaneously, causing race conditions.
|
||||
Atomic variable: Operations are performed atomically, preventing race conditions for single-variable updates.
|
||||
|
||||
2 - Name at least four classes from the java.util.concurrent.atomic package that provide atomic operations for different data types.
|
||||
|
||||
AtomicInteger
|
||||
AtomicLong
|
||||
AtomicBoolean
|
||||
AtomicReference<T>
|
||||
|
||||
Typical use case for AtomicInteger:
|
||||
AtomicInteger is commonly used as a thread-safe counter when multiple threads need to increment a shared value concurrently.
|
||||
|
||||
3 - Compare locks with atomic variables.
|
||||
|
||||
atomic variables:
|
||||
|
||||
Advantages:
|
||||
|
||||
Faster than locks for simple operations.
|
||||
Non-blocking.
|
||||
Lower overhead.
|
||||
|
||||
Best for:
|
||||
|
||||
Counters.
|
||||
Flags.
|
||||
Single-variable updates.
|
||||
Locks
|
||||
|
||||
locks:
|
||||
|
||||
Advantages:
|
||||
Can protect multiple variables together.
|
||||
Support complex critical sections.
|
||||
|
||||
Best for:
|
||||
|
||||
Operations involving multiple shared objects.
|
||||
Complex business logic requiring mutual exclusion.
|
||||
|
||||
Summary:
|
||||
|
||||
Use atomic variables for simple thread-safe updates.
|
||||
Use locks when multiple operations must be performed as one atomic unit.
|
||||
|
||||
Bonus Task
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class AtomicDemo
|
||||
{
|
||||
static int normalCounter = 0;
|
||||
static AtomicInteger atomicCounter = new AtomicInteger(0);
|
||||
|
||||
public static void main(String[] args) throws InterruptedException
|
||||
{
|
||||
|
||||
int numThreads = 10;
|
||||
int incrementsPerThread = 100000;
|
||||
|
||||
Thread[] threads = new Thread[numThreads];
|
||||
|
||||
for (int i = 0; i < numThreads; i++) {
|
||||
threads[i] = new Thread(() -> {
|
||||
for (int j = 0; j < incrementsPerThread; j++) {
|
||||
normalCounter++;
|
||||
atomicCounter.incrementAndGet();
|
||||
}
|
||||
});
|
||||
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
for (Thread thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
System.out.println("Normal Counter: " + normalCounter);
|
||||
System.out.println("Atomic Counter: " + atomicCounter.get());
|
||||
}
|
||||
}
|
||||
|
||||
Expected Result:
|
||||
|
||||
Normal Counter: 823451 (varies)
|
||||
Atomic Counter: 1000000
|
||||
|
||||
The normal counter may be incorrect because multiple threads overwrite each other's updates. The atomic counter always produces the correct result.
|
||||
|
||||
4 - A program is completely free of race conditions but still performs poorly under high contention.
|
||||
Explain how this situation can occur.
|
||||
|
||||
A program can be completely correct yet still perform poorly because threads spend too much time waiting for shared resources.
|
||||
|
||||
Three factors that limit scalability:
|
||||
|
||||
Lock Contention
|
||||
Many threads compete for the same lock and must wait.
|
||||
|
||||
Synchronization Overhead
|
||||
Acquiring and releasing locks consumes CPU time.
|
||||
|
||||
Thread Blocking
|
||||
Threads frequently pause while waiting for locks or resources, reducing parallelism.
|
||||
|
||||
Correctness is guaranteed, but throughput decreases as contention increases.
|
||||
|
||||
5 - Many concurrent systems experience performance degradation as the number of threads increases.
|
||||
Explain why adding more threads does not always improve performance.
|
||||
|
||||
The CPU must save and restore thread state when switching between threads. Too many threads increase this overhead.
|
||||
|
||||
Contention
|
||||
More threads compete for the same locks and resources, causing waiting.
|
||||
|
||||
Cache Coherence
|
||||
When multiple CPUs modify shared data, cache contents must be synchronized, creating extra communication overhead.
|
||||
|
||||
Synchronization Overhead
|
||||
Locks, atomic operations, and coordination mechanisms consume time and reduce the benefits of parallel execution.
|
||||
|
||||
Conclusion: After a certain point, adding more threads increases overhead more than useful work, causing performance to stagnate or even decrease.
|
||||
|
||||
6 - Deadlocks often only appear in production, not during testing.Explain why this might happen from a thread-scheduling perspective.
|
||||
|
||||
Deadlocks depend on the exact timing and scheduling of threads.
|
||||
During testing, threads may execute in a favorable order and never enter the deadlock state. In production, different workloads, hardware, and timing conditions can cause the problematic scheduling sequence to occur.
|
||||
|
||||
Two ways to expose deadlocks during testing
|
||||
|
||||
1. Increase concurrency
|
||||
Run with many threads.execute tests repeatedly under heavy load.
|
||||
|
||||
|
||||
2. Introduce timing variations
|
||||
Add random delays (Thread.sleep()).
|
||||
Use stress-testing tools to force different thread interleavings.
|
||||
|
||||
These techniques increase the chance that threads acquire locks in a problematic order, revealing deadlocks before deployment.
|
||||
Reference in New Issue
Block a user