1 Commits
Author SHA1 Message Date
Soroush 1e7d204322 Assignment-9 2026-06-20 00:18:49 +03:30
9 changed files with 87 additions and 109 deletions
+47
View File
@@ -0,0 +1,47 @@
### 1. Atomic Variables & Synchronization
#### What are atomic variables and what are they used for?
- Atomic variables are specialized variables that handle updates in a single, uninterruptible step.
They allow multiple threads to work on the same data safely without needing to use manual locks.
#### Name at least four classes from the java.util.concurrent.atomic package that provide atomic operations for different data types, and briefly describe a typical use case for one of them.
- Four common classes from the package are:
```
AtomicInteger (for int values)
AtomicLong (for long values)
AtomicBoolean (for boolean values)
AtomicReference (for object references)
```
For example, `AtomicInteger` is usually used for high-performance counters that need to be updated by multiple threads simultaneously.
#### Compare locks with atomic variables. Explain where each one is usually used.
- Atomic variables are usually used when we just need to update a single variable or a flag since they're faster and simpler.
But locks are usually used when dealing with multiple dependent variables or complex logic and conditions.
---
### 2. Locks & Concurrent Design
#### Explain why a program could be completely free of race conditions but still perform poorly under high contention. Discuss at least three concurrency-related factors that may limit scalability even when correctness is guaranteed.
- Correctness doesn't always mean scalability.
1. Coarse-Grained Locking: Locking a large section of code instead of just the piece you need.
2. Lock Contention: Having too many threads fighting to grab the exact same lock at the exact same time.
3. Thread Orchestration Overhead: The Operating System using significant CPU power just to "manage" threads (switching, waking, and pausing) rather than actually running the code.
#### Explain why adding more threads does not always improve performance. Discuss what concepts like Context switching, Contention, Cache coherence, and Synchronization overhead mean.
- Having too many threads does not necessarily mean better performance.
1. Context switching: Changing from thread A to thread B requires saving and loading thread states,
Which spends CPU power.
2. Contention: More threads mean more competition for limited resources, which leads to threads spending their time waiting for locks to release instead of processing data.
3. Cache coherence: When cores share data, they have to constantly sync their local caches. This interaction between CPU cores slows everything down.
4. Synchronization overhead: Acquiring and releasing locks require extra CPU instructions. If the code uses too many locks, the CPU spends all its time checking safety protocols instead of doing the actual work.
---
### 3. Deadlocks
#### Explain why Deadlocks often only appear in production, not during testing from a thread-scheduling perspective.
- Deadlocks are timing-dependent. They only occur when threads interleave in a very specific, rare order that often doesnt happen under light testing loads.
#### Describe two strategies a developer can use to increase the likelihood of exposing deadlocks during testing.
- We can use some strategies, such as:
1. High-Concurrency Stress Testing: Using a large number of threads performing random operations simultaneously to trigger high-concurrency scenarios that might not happen with just 23 threads.
2. Injecting Artificial Timing Delays: Adding a random `Thread.sleep(10)` before acquiring and releasing locks or during other high stress moments. This forces the threads to encounter non-deterministic execution paths which makes timing-based bugs much easier to spot.
@@ -8,12 +8,6 @@ import dev.banking.service.BankingSystem;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
/**
* DemoApplication is responsible for:
* - Building the system
* - Running the simulation
* - Managing lifecycle (threads, schedulers)
*/
public final class DemoApplication { public final class DemoApplication {
private DemoApplication() { private DemoApplication() {
@@ -23,9 +17,6 @@ public final class DemoApplication {
System.out.println("Initializing Banking Simulation...\n"); System.out.println("Initializing Banking Simulation...\n");
/*
* 1. Create bank accounts
*/
BankAccount acc1 = new BankAccount(1, 1000); BankAccount acc1 = new BankAccount(1, 1000);
BankAccount acc2 = new BankAccount(2, 2000); BankAccount acc2 = new BankAccount(2, 2000);
BankAccount acc3 = new BankAccount(3, 1500); BankAccount acc3 = new BankAccount(3, 1500);
@@ -35,9 +26,6 @@ public final class DemoApplication {
accounts.put(2, acc2); accounts.put(2, acc2);
accounts.put(3, acc3); accounts.put(3, acc3);
/*
* 2. Create transactions
*/
List<Transaction> transactions = List.of( List<Transaction> transactions = List.of(
new DepositTransaction(1, 200), new DepositTransaction(1, 200),
new WithdrawTransaction(2, 300), new WithdrawTransaction(2, 300),
@@ -48,24 +36,12 @@ public final class DemoApplication {
new TransferTransaction(3, 1, 250) new TransferTransaction(3, 1, 250)
); );
/*
* 3. Thread pool (workers)
*/
ExecutorService executor = Executors.newFixedThreadPool(4); ExecutorService executor = Executors.newFixedThreadPool(4);
/*
* 4. Processor
*/
TransactionProcessor processor = new TransactionProcessor(accounts); TransactionProcessor processor = new TransactionProcessor(accounts);
/*
* 5. Banking system
*/
BankingSystem bankingSystem = new BankingSystem(executor, processor); BankingSystem bankingSystem = new BankingSystem(executor, processor);
/*
* 6. Live monitor (UI simulation)
*/
LiveMonitor monitor = new LiveMonitor(); LiveMonitor monitor = new LiveMonitor();
ScheduledExecutorService monitorExecutor = Executors.newSingleThreadScheduledExecutor(); ScheduledExecutorService monitorExecutor = Executors.newSingleThreadScheduledExecutor();
@@ -75,14 +51,8 @@ public final class DemoApplication {
System.out.println("----------------------"); System.out.println("----------------------");
}, 0, 1, TimeUnit.SECONDS); }, 0, 1, TimeUnit.SECONDS);
/*
* 7. Run simulation
*/
bankingSystem.processTransactions(transactions); bankingSystem.processTransactions(transactions);
/*
* 8. Shutdown / lifecycle management
*/
shutdown(executor, monitorExecutor); shutdown(executor, monitorExecutor);
System.out.println("\nSimulation completed."); System.out.println("\nSimulation completed.");
-6
View File
@@ -1,11 +1,5 @@
package dev.banking; package dev.banking;
/**
* Entry point of the application.
*
* IMPORTANT:
* - This is only a wrapper for running the demo.
*/
public final class Main { public final class Main {
private Main() { private Main() {
@@ -1,18 +1,17 @@
package dev.banking.model; package dev.banking.model;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class BankAccount { public class BankAccount {
private final int accountId; private final int accountId;
private long balance; private long balance;
/* private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
* Students may introduce additional fields private final Lock readLock = rwLock.readLock();
* such as: private final Lock writeLock = rwLock.writeLock();
* - Lock / ReentrantLock
* - ReadWriteLock
* - Object monitor
* - etc.
*/
public BankAccount(int accountId, long initialBalance) { public BankAccount(int accountId, long initialBalance) {
this.accountId = accountId; this.accountId = accountId;
@@ -23,56 +22,48 @@ public class BankAccount {
return accountId; 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() { public long getBalance() {
throw new UnsupportedOperationException("TODO: implement thread-safe balance read"); readLock.lock();
try {
return balance;
} finally {
readLock.unlock();
}
} }
/*
* TODO:
* Increase balance atomically.
*
* Requirements:
* - Must not lose updates under concurrency
*/
public void deposit(long amount) { public void deposit(long amount) {
throw new UnsupportedOperationException("TODO: implement thread-safe deposit"); writeLock.lock();
try {
balance += amount;
} finally {
writeLock.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) { public void withdraw(long amount) {
throw new UnsupportedOperationException("TODO: implement thread-safe withdraw"); writeLock.lock();
try {
balance -= amount;
} finally {
writeLock.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) { public void transfer(BankAccount target, long amount) {
throw new UnsupportedOperationException("TODO: implement atomic deadlock-free transfer"); BankAccount firstLock = this.accountId < target.accountId ? this : target;
BankAccount secondLock = this.accountId < target.accountId ? target : this;
firstLock.writeLock.lock();
try {
secondLock.writeLock.lock();
try {
this.balance -= amount;
target.balance += amount;
} finally {
secondLock.writeLock.unlock();
}
} finally {
firstLock.writeLock.unlock();
}
} }
} }
@@ -1,8 +1,5 @@
package dev.banking.model; package dev.banking.model;
/**
* Represents a deposit operation.
*/
public final class DepositTransaction extends Transaction { public final class DepositTransaction extends Transaction {
private final int accountId; private final int accountId;
@@ -1,8 +1,5 @@
package dev.banking.model; package dev.banking.model;
/**
* Base class for all transaction types.
*/
public abstract class Transaction { public abstract class Transaction {
private final int amount; private final int amount;
@@ -1,8 +1,5 @@
package dev.banking.model; package dev.banking.model;
/**
* Represents a transfer operation between two accounts.
*/
public final class TransferTransaction public final class TransferTransaction
extends Transaction { extends Transaction {
@@ -1,8 +1,5 @@
package dev.banking.model; package dev.banking.model;
/**
* Represents a withdrawal operation.
*/
public final class WithdrawTransaction extends Transaction { public final class WithdrawTransaction extends Transaction {
private final int accountId; private final int accountId;
@@ -6,18 +6,6 @@ import dev.banking.processor.TransactionProcessor;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService; 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 { public class BankingSystem {
private final ExecutorService executor; private final ExecutorService executor;