148 lines
4.8 KiB
Markdown
148 lines
4.8 KiB
Markdown
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. |