Merge pull request 'HW 09' (#1) from develop into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="HW-09-Advanced-Multithreading" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+20
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://maven.devneeds.ir/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="25" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
## ⚛️ Atomic Variables & Synchronization
|
||||
|
||||
---
|
||||
|
||||
|
||||
### **1 -** Atomic variables solve thread-safety problems without using explicit synchronization (synchronized blocks or Lock objects). Their primary purposes are:
|
||||
|
||||
1. Provide thread-safe operations on single variables without locks
|
||||
|
||||
2. Enable non-blocking algorithms with better performance under moderate contention
|
||||
|
||||
3. Ensure visibility across threads (like volatile variables)
|
||||
|
||||
4. Support atomic read-modify-write operations (e.g., increment, compare-and-set)
|
||||
|
||||
|
||||
### **2 -** Four Atomic Classes from java.util.concurrent.atomic:
|
||||
|
||||
1. **AtomicInteger:** Atomic operations for int values
|
||||
|
||||
2. **AtomicLong:** Atomic operations for long values
|
||||
|
||||
3. **AtomicBoolean:** Atomic operations for boolean values
|
||||
|
||||
4. **AtomicReference<V>**: Atomic operations for object references
|
||||
|
||||
- A use case is when a web server needs to generate unique, sequential request IDs across thousands of concurrent threads.
|
||||
|
||||
|
||||
### **3 -** When to use…
|
||||
|
||||
- **Atomic Variables:** when you're updating only one variable with simple operations under moderate contention.
|
||||
|
||||
- **Locks:** when you need to coordinate multiple resources, have complex conditions, or require fairness or blocking semantics.
|
||||
|
||||
| Aspect | Locks (synchronized, ReentrantLock) | Atomic Variables |
|
||||
|----------------------|--------------------------------------------|-------------------------|
|
||||
| Mechanism | Blocking (threads wait/park) | Non-blocking (CAS operations) |
|
||||
| Scope Can | protect multiple variables/operations | Single variable only |
|
||||
| Overhead | Higher (context switching, OS involvement) | Lower (CPU-level instructions) |
|
||||
| Contention handling | Threads block and may be descheduled | Threads retry without blocking |
|
||||
| Deadlock risk | Yes (especially with multiple locks) | No |
|
||||
| Fairness options | Available (e.g., ReentrantLock(true)) | No (CAS is inherently unfair) |
|
||||
| Composite operations | Easy (multiple steps together) | Difficult or impossible |
|
||||
|
||||
|
||||
### **- Bonus:**
|
||||
|
||||
```java
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class example {
|
||||
private static int count1;
|
||||
private static AtomicInteger count2 = new AtomicInteger(0);
|
||||
|
||||
public example() {}
|
||||
|
||||
public static void increment() { count1++; }
|
||||
|
||||
public static void atomicIncrement() { count2.incrementAndGet(); }
|
||||
|
||||
public static void main(String args[]) throws InterruptedException {
|
||||
|
||||
Runnable r = () -> {
|
||||
for (int i = 1; i <= 1000; i++) {
|
||||
atomicIncrement();
|
||||
increment();
|
||||
}
|
||||
};
|
||||
|
||||
Thread t1 = new Thread(r, "thread-1");
|
||||
Thread t2 = new Thread(r, "thread-2");
|
||||
Thread t3 = new Thread(r, "thread-3");
|
||||
|
||||
t1.start();
|
||||
t2.start();
|
||||
t3.start();
|
||||
|
||||
t1.join();
|
||||
t2.join();
|
||||
t3.join();
|
||||
|
||||
System.out.println("-int- variable value: " + count1 + " -AtomicInteger- variable value: " + count2);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
```text
|
||||
-int- variable value: 2971 -AtomicInteger- variable value: 3000
|
||||
```
|
||||
|
||||
|
||||
## 🔒 Locks & Concurrent Design
|
||||
|
||||
---
|
||||
|
||||
|
||||
### **4 -** A program can be free of race conditions through proper synchronization (locks, atomic variables, concurrent collections), but still suffer from:
|
||||
|
||||
- **Contention:** Threads competing for the same resources
|
||||
|
||||
- **Over-synchronization:** Too much or too coarse-grained locking
|
||||
|
||||
- **Hardware limitations:** Cache coherence traffic, false sharing
|
||||
|
||||
Concurrency-related factors that may limit scalability even when correctness is guaranteed:
|
||||
|
||||
1. **Lock Contention (Serialization):**
|
||||
Even with correct locking, if threads frequently wait for locks, execution becomes effectively serialized. Threads queue up despite having multiple CPU cores.
|
||||
|
||||
2. **Cache Coherence Traffic (The "Hidden" Contention):**
|
||||
Even lock-free atomic variables generate significant CPU cache traffic that limits scalability.
|
||||
|
||||
3. **False Sharing:**
|
||||
When threads modify different variables that accidentally share the same CPU cache line, the cache coherence protocol treats them as if they were the same variable.
|
||||
|
||||
|
||||
### **5 -** This is the scalability ceiling problem. Adding threads eventually hurts performance due to coordination overhead.
|
||||
|
||||
1. **Context Switching Overhead:**
|
||||
When more threads than CPU cores exist, the OS constantly switches between threads, saving/restoring state.
|
||||
|
||||
2. **Contention for Shared Resources:**
|
||||
As threads increase, probability of conflict superlinearly increases.
|
||||
|
||||
3. **Cache Coherence Traffic:**
|
||||
Modern CPUs maintain cache coherence (MESI protocol). When multiple cores modify shared data, they generate coherence traffic that doesn't exist with fewer threads.
|
||||
|
||||
4. **Synchronization Overhead (Locking):**
|
||||
Even without contention, acquiring/releasing locks has intrinsic overhead.
|
||||
|
||||
|
||||
## ⚠️ Deadlocks
|
||||
|
||||
---
|
||||
|
||||
|
||||
### **6 -** A deadlock requires four conditions:
|
||||
|
||||
- **Mutual exclusion:** Resources can't be shared
|
||||
|
||||
- **Hold and wait:** Thread holds one resource while waiting for another
|
||||
|
||||
- **No preemption:** Resources can't be forcibly taken
|
||||
|
||||
- **Circular wait:** Threads form a cycle of dependencies
|
||||
|
||||
But even with all conditions present, a deadlock only occurs when threads acquire locks in precisely the wrong order at precisely the wrong time.
|
||||
Specific scheduling factors that hide deadlocks:
|
||||
|
||||
1. **Low contention in tests:** Threads rarely overlap in lock acquisition
|
||||
|
||||
2. **Fast operations:** Critical sections so brief that interleaving is improbable
|
||||
|
||||
3. **Small thread pools:** Fewer permutations of lock ordering
|
||||
|
||||
4. **Deterministic scheduling:** Same interleaving every run (lulls developers into false safety)
|
||||
|
||||
5. **No GC pressure:** GC can pause threads at vulnerable moments
|
||||
|
||||
*Strategy 1 :* Manual Lock Order Inversion (Eclipse Contest)
|
||||
Force the circular wait condition by deliberately inverting lock order in tests.
|
||||
|
||||
*Strategy 2 :* Inject Preemptive Scheduling Points (ThreadWeaver)
|
||||
Force thread context switches at vulnerable points using controlled test frameworks like ThreadWeaver or JCStress.
|
||||
@@ -18,7 +18,25 @@
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>5.12.2</version>
|
||||
<version>5.11.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -28,7 +46,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.5.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package dev.banking.model;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class BankAccount {
|
||||
|
||||
private final int accountId;
|
||||
private long balance;
|
||||
private ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
/*
|
||||
* Students may introduce additional fields
|
||||
@@ -31,8 +36,8 @@ public class BankAccount {
|
||||
* - Must be safe under concurrent reads/writes
|
||||
* - Should not block unnecessarily if using read/write locks
|
||||
*/
|
||||
public long getBalance() {
|
||||
throw new UnsupportedOperationException("TODO: implement thread-safe balance read");
|
||||
public synchronized long getBalance() { //??????????????????????????/?
|
||||
return balance;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -42,8 +47,8 @@ public class BankAccount {
|
||||
* Requirements:
|
||||
* - Must not lose updates under concurrency
|
||||
*/
|
||||
public void deposit(long amount) {
|
||||
throw new UnsupportedOperationException("TODO: implement thread-safe deposit");
|
||||
public synchronized void deposit(long amount) {
|
||||
balance += amount;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -55,8 +60,8 @@ public class BankAccount {
|
||||
* - Negative balance handling is NOT required unless you decide
|
||||
* to extend the system (optional)
|
||||
*/
|
||||
public void withdraw(long amount) {
|
||||
throw new UnsupportedOperationException("TODO: implement thread-safe withdraw");
|
||||
public synchronized void withdraw(long amount) {
|
||||
balance -= amount;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -73,6 +78,14 @@ public class BankAccount {
|
||||
* - Or tryLock with retry strategy
|
||||
*/
|
||||
public void transfer(BankAccount target, long amount) {
|
||||
throw new UnsupportedOperationException("TODO: implement atomic deadlock-free transfer");
|
||||
BankAccount first = target.getAccountId() < accountId ? target : this ;
|
||||
BankAccount second = target.getAccountId() < accountId ? this : target ;
|
||||
|
||||
synchronized (first) {
|
||||
synchronized (second) {
|
||||
target.deposit(amount);
|
||||
this.withdraw(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user