Advanced Multithreading

This commit is contained in:
2026-06-12 23:53:54 +03:30
parent fa95f2ebeb
commit 5fc538a623
8 changed files with 365 additions and 58 deletions
+229
View File
@@ -0,0 +1,229 @@
# Ninth Assignment — Answers
---
### ⚛️ Atomic Variables & Synchronization
---
### **1 - What are atomic variables?**
Atomic variables are variables that support **lock-free, thread-safe operations**. This means operations on them (such as increment, compare-and-set, etc.) are performed as a single indivisible step.
Their main purpose is to **prevent race conditions** when multiple threads access and modify shared data concurrently.
#### Difference from ordinary variables:
* **Ordinary variables (non-atomic):**
* Operations like `x++` are NOT atomic.
* They consist of multiple steps: read → modify → write.
* This can lead to race conditions.
* **Atomic variables:**
* Provide built-in thread-safe operations.
* Use low-level CPU instructions (like CAS Compare-And-Swap).
* No need for explicit synchronization (like `synchronized` or locks).
---
### **2 - Atomic classes in `java.util.concurrent.atomic`**
Examples of atomic classes:
* `AtomicInteger`
* `AtomicLong`
* `AtomicBoolean`
* `AtomicReference`
#### Example use case: `AtomicInteger`
`AtomicInteger` is commonly used as a **thread-safe counter**.
Example:
* Counting number of requests in a web server
* Tracking successful operations across multiple threads
It avoids race conditions without using locks, improving performance under moderate contention.
---
### **3 - Locks vs Atomic Variables**
#### Atomic Variables:
**Advantages:**
* Faster (lock-free)
* No blocking
* Simple for single-variable operations
**Limitations:**
* Only suitable for **simple operations**
* Not useful for multi-step or multi-variable logic
---
#### Locks (`synchronized`, `ReentrantLock`):
**Advantages:**
* Can protect **complex operations**
* Allow coordination across multiple variables
* Support conditions and waiting
**Disadvantages:**
* Slower due to blocking
* Can cause deadlocks if misused
---
#### When to use which?
* Use **Atomic Variables** when:
* You have simple operations (e.g., increment counter)
* No need for coordination between multiple variables
* Use **Locks** when:
* Multiple shared variables are involved
* You need atomic multi-step operations
* You need waiting/notification mechanisms
---
### 🎯 Bonus Task — Example Program
```java
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 threads = 10;
Thread[] workers = new Thread[threads];
for (int i = 0; i < threads; i++) {
workers[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
normalCounter++; // NOT thread-safe
atomicCounter.incrementAndGet(); // thread-safe
}
});
workers[i].start();
}
for (Thread t : workers) {
t.join();
}
System.out.println("Normal Counter: " + normalCounter);
System.out.println("Atomic Counter: " + atomicCounter.get());
}
}
```
Expected result:
* `atomicCounter` will always be correct (10000)
* `normalCounter` may be less due to race conditions
---
### 🔒 Locks & Concurrent Design
---
### **4 - Race-free but poor performance**
A program can be completely correct (no race conditions) but still perform poorly due to **high contention and synchronization overhead**.
#### Reasons:
1. **High contention**
* Many threads compete for the same lock
* Threads spend time waiting instead of doing work
2. **Excessive synchronization**
* Overuse of locks reduces parallelism
* Even independent operations get blocked
3. **Lock granularity issues**
* Coarse-grained locks (one big lock)
* Prevent multiple threads from working in parallel
---
### **5 - Why more threads ≠ better performance**
Adding more threads can hurt performance due to:
#### 1. Context Switching
* CPU switches between threads
* This has overhead and wastes time
#### 2. Contention
* Threads compete for shared resources (locks, memory)
* More threads → more waiting
#### 3. Cache Coherence
* CPUs maintain consistency of cached data
* Frequent updates cause cache invalidation
* Slows down execution
#### 4. Synchronization Overhead
* Locks and coordination add extra cost
* More threads → more synchronization
---
### ⚠️ Deadlocks
---
### **6 - Why deadlocks appear in production**
Deadlocks depend on **thread scheduling**, which is:
* Non-deterministic
* Timing-dependent
In testing:
* Fewer threads
* Predictable execution
In production:
* High concurrency
* Different timing → circular waits may occur
---
#### Strategies to expose deadlocks:
1. **Stress testing**
* Run with many threads
* Increase contention
* Repeat tests multiple times
2. **Introduce artificial delays**
* Add `sleep()` between lock acquisitions
* Makes timing issues more visible
* Increases probability of deadlock