Implement all TODOs

This commit is contained in:
2026-06-13 16:46:39 +03:30
parent fa95f2ebeb
commit 96532c56da
15 changed files with 154 additions and 98 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="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>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://mirror-maven.runflare.com/maven2" />
</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="openjdk-25" project-jdk-type="JavaSDK" />
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+48
View File
@@ -0,0 +1,48 @@
# Java Concurrency: Atomic Variables & Locks
### 1. Atomic Variables
Atomic variables are tools that let us update a value safely when many threads are running. They ensure that an action (like adding 1) happens **all at once** without being interrupted.
* **Difference:** Normal variables can lose data if two threads change them at the exact same time. Atomic variables use a "try-and-retry" method (CAS) to stay accurate without stopping other threads.
### 2. Common Atomic Classes
* `AtomicInteger`
* `AtomicLong`
* `AtomicBoolean`
* `AtomicReference`
**Use Case:** We use `AtomicInteger` for a **hit counter** on a website. It keeps the total count correct even if thousands of users click a button at the same second.
### 3. Locks vs. Atomic Variables
| Feature | Atomic Variables | Locks (`synchronized`) |
| :--- | :--- | :--- |
| **Speed** | Very Fast | Slower |
| **Thread Behavior** | Keep running (no waiting) | Stop and wait their turn |
| **Best For** | Single numbers or flags | Groups of variables or big tasks |
* **We use Atomic Variables when:** We only need to change one value quickly and want to avoid the slowdown of locking.
* **We use Locks when:** We need to update **multiple related values** together or when the work takes a long time (like saving a file).
### 4. Poor Performance without Race Conditions
Even if our code is "correct" (no data is lost), it can still be slow if many threads fight for the same resources. This is called **high contention**.
**Factors that limit scaling:**
1. **Lock Contention:** Many threads wait in a long line for one lock, so only one thread actually works at a time.
2. **Context Switching:** The CPU spends more time "swapping" threads in and out than actually running our code.
3. **Memory Bottlenecks:** Threads might be waiting for data to move between the main memory and the CPU.
### 5. Why More Threads Can Slow Us Down
Adding more threads has a "cost" that eventually outweighs the benefits.
- **Context Switching:** Every time the CPU moves from one thread to another, it has to save and load data. This wastes time.
- **Contention:** If we have 100 threads but only 1 resource, 99 threads are sitting idle, which wastes memory.
- **Cache Coherence:** When one thread changes data, the CPU must tell all other cores to update their private "caches." This constant communication slows down the whole system.
- **Synchronization Overhead:** The tools we use to stay safe (like locks or atomic signals) require extra CPU work to manage.
### 6. Deadlocks in Production vs. Testing
Deadlocks depend on **timing**. In testing, threads might always run in a "safe" order because the computer is less busy. In production, unexpected delays or high traffic can cause threads to grab locks in the "wrong" order, causing a freeze.
**Strategies to find deadlocks during testing:**
1. **Thread Fuzzing:** We can add random, tiny delays (like `Thread.sleep()`) in our code during testing. This forces different timings and helps expose hidden deadlocks.
2. **Stress Testing:** We can run the program with a much higher number of threads and data than we expect. This makes it more likely that the rare "wrong timing" will happen.
@@ -1,18 +1,13 @@
package dev.banking.model;
import java.util.concurrent.locks.Lock;
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 Lock lock = new ReentrantLock();
public BankAccount(int accountId, long initialBalance) {
this.accountId = accountId;
@@ -23,56 +18,48 @@ public class BankAccount {
return accountId;
}
/*
* 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");
lock.lock();
try {
return balance;
} 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");
lock.lock();
try {
balance += amount;
} 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");
lock.lock();
try {
balance -= amount;
} finally {
lock.unlock();
}
}
/*
* 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();
try {
second.lock.lock();
try {
this.withdraw(amount);
target.deposit(amount);
} finally {
second.lock.unlock();
}
} finally {
first.lock.unlock();
}
}
}
@@ -1,8 +1,5 @@
package dev.banking.model;
/**
* Represents a deposit operation.
*/
public final class DepositTransaction extends Transaction {
private final int accountId;
@@ -1,8 +1,5 @@
package dev.banking.model;
/**
* Base class for all transaction types.
*/
public abstract class Transaction {
private final int amount;
@@ -1,8 +1,5 @@
package dev.banking.model;
/**
* Represents a transfer operation between two accounts.
*/
public final class TransferTransaction
extends Transaction {
@@ -1,8 +1,5 @@
package dev.banking.model;
/**
* Represents a withdrawal operation.
*/
public final class WithdrawTransaction extends Transaction {
private final int accountId;
@@ -1,7 +1,6 @@
package dev.banking.monitor;
import dev.banking.model.BankAccount;
import java.util.Collection;
public class LiveMonitor {
@@ -9,15 +8,12 @@ public class LiveMonitor {
public void update(
Collection<BankAccount> accounts
) {
for (BankAccount account : accounts) {
System.out.printf(
"Account %d -> %d%n",
account.getAccountId(),
account.getBalance()
);
}
}
}
@@ -1,7 +1,6 @@
package dev.banking.processor;
import dev.banking.model.*;
import java.util.Map;
public class TransactionProcessor {
@@ -15,33 +14,19 @@ public class TransactionProcessor {
}
public void process(Transaction tx) {
if (tx instanceof DepositTransaction deposit) {
BankAccount account = accounts.get(deposit.getAccountId());
account.deposit(deposit.getAmount());
}
else if (tx instanceof WithdrawTransaction withdraw) {
BankAccount account = accounts.get(withdraw.getAccountId());
account.withdraw(withdraw.getAmount());
}
else if (tx instanceof TransferTransaction transfer) {
BankAccount source = accounts.get(transfer.getSourceAccountId());
BankAccount target = accounts.get(transfer.getTargetAccountId());
source.transfer(
target,
transfer.getAmount()
);
source.transfer(target, transfer.getAmount());
}
else {
throw new IllegalArgumentException(
"Unknown transaction type: " + tx.getClass()
@@ -2,22 +2,9 @@ package dev.banking.service;
import dev.banking.model.*;
import dev.banking.processor.TransactionProcessor;
import java.util.List;
import java.util.concurrent.ExecutorService;
/**
* Dispatches a list of transactions to a shared ExecutorService
* for concurrent (asynchronous) processing.
*
* Each transaction is submitted as an independent task and may
* be executed in parallel depending on thread availability.
*
* No ordering guarantees are provided between transactions.
*
* Lifecycle management of the ExecutorService (creation,
* shutdown, termination) is handled outside this class.
*/
public class BankingSystem {
private final ExecutorService executor;
@@ -34,13 +21,10 @@ public class BankingSystem {
public void processTransactions(
List<Transaction> transactions
) {
for (Transaction tx : transactions) {
executor.submit(() -> {
processor.process(tx);
});
}
}
}