Files
2026-06-12 23:53:54 +03:30

229 lines
5.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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