2 Commits
8 changed files with 365 additions and 58 deletions
+10
View File
@@ -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/
+13
View File
@@ -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>
+7
View File
@@ -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>
+20
View File
@@ -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>
+12
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+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
@@ -1,78 +1,87 @@
package dev.banking.model;
public class BankAccount {
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount
{
private final int accountId;
private long balance;
/*
* Students may introduce additional fields
* such as:
* - Lock / ReentrantLock
* - ReadWriteLock
* - Object monitor
* - etc.
*/
private final ReentrantLock lock = new ReentrantLock();
private final Condition sufficientFunds = lock.newCondition();
public BankAccount(int accountId, long initialBalance) {
public BankAccount(int accountId, long initialBalance)
{
this.accountId = accountId;
this.balance = initialBalance;
}
public int getAccountId() {
return accountId;
public int getAccountId() {return accountId;}
public long getBalance()
{
lock.lock();
try {return balance;}
finally {lock.unlock();}
}
/*
* TODO:
* Return the current balance in a thread-safe way.
*
* Requirements:
* - 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 void deposit(long amount)
{
lock.lock();
try
{
balance += amount;
sufficientFunds.signalAll(); // wake waiting threads
}
finally {lock.unlock();}
}
/*
* TODO:
* Increase balance atomically.
*
* Requirements:
* - Must not lose updates under concurrency
*/
public void deposit(long amount) {
throw new UnsupportedOperationException("TODO: implement thread-safe deposit");
public void withdraw(long amount)
{
lock.lock();
try
{
while (balance < amount) {sufficientFunds.await();}
balance -= amount;
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
finally {lock.unlock();
}
}
/*
* TODO:
* Decrease balance atomically.
*
* Requirements:
* - Must not cause race conditions
* - 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 void transfer(BankAccount target, long amount)
{
if (this == target) return;
/*
* TODO:
* Transfer money between two accounts atomically.
*
* IMPORTANT REQUIREMENTS:
* - Must be atomic (no partial transfer)
* - Must be deadlock-free
* - Must protect both source and target accounts
*
* HINT:
* - Consider global lock ordering using accountId
* - Or tryLock with retry strategy
*/
public void transfer(BankAccount target, long amount) {
throw new UnsupportedOperationException("TODO: implement atomic deadlock-free transfer");
BankAccount first = this.accountId < target.accountId ? this : target;
BankAccount second = this.accountId < target.accountId ? target : this;
first.lock.lock();
second.lock.lock();
try
{
while (this.balance < amount) {this.sufficientFunds.await();}
this.balance -= amount;
target.balance += amount;
this.sufficientFunds.signalAll();
target.sufficientFunds.signalAll();
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
finally
{
second.lock.unlock();
first.lock.unlock();
}
}
}