Merge pull request 'Dev' (#3) from dev into main
Reviewed-on: AdvancedProgramming1404/HW-09-Advanced-Multithreading#3
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@@ -1,2 +1,343 @@
|
|||||||
# HW-09-Advanced-Multithreading
|
# Ninth Assignment — Advanced Multithreading
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
## 📋 Introduction
|
||||||
|
This assignment is divided into two main sections:
|
||||||
|
|
||||||
|
### **Theoretical Questions**:
|
||||||
|
- You are asked to answer questions about multithreading concepts.
|
||||||
|
|
||||||
|
### **Practical Project**:
|
||||||
|
- You are asked to implement a banking system that performs concurrent operations using multithreading. The main goal of the exercise is handling deadlocks and race conditions so that the program performs correctly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Theoretical Questions
|
||||||
|
|
||||||
|
### **Note**:
|
||||||
|
**Write your answers in a Markdown file (e.g. Answers.md) and place it in the root directory of your forked repository.**
|
||||||
|
|
||||||
|
### ⚛️ Atomic Variables & Synchronization
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**1 -** What are atomic variables?
|
||||||
|
|
||||||
|
Explain their purpose and how they differ from ordinary (non-atomic) variables.
|
||||||
|
|
||||||
|
**2 -** Name at least four classes from the `java.util.concurrent.atomic` package that provide atomic operations for different data types.
|
||||||
|
|
||||||
|
For one of them, briefly describe a typical use case.
|
||||||
|
|
||||||
|
**3 -** Compare locks with atomic variables.
|
||||||
|
|
||||||
|
In which scenarios is using a lock a better choice than an atomic variable, and vice versa?
|
||||||
|
|
||||||
|
#### 🎯Bonus Task:
|
||||||
|
|
||||||
|
Write a small Java program that concurrently increments a normal int variable and an AtomicInteger variable from multiple threads. Print the final values of both variables to demonstrate that the atomic variable produces the correct result, while the normal variable may show an inconsistent value due to a race condition.
|
||||||
|
|
||||||
|
### 🔒 Locks & Concurrent Design
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**4 -** A program is completely free of race conditions but still performs poorly under high contention.
|
||||||
|
|
||||||
|
Explain how this situation can occur.
|
||||||
|
|
||||||
|
Discuss at least three concurrency-related factors that may limit scalability even when correctness is guaranteed.
|
||||||
|
|
||||||
|
**5 -** Many concurrent systems experience performance degradation as the number of threads increases.
|
||||||
|
|
||||||
|
Explain why adding more threads does not always improve performance.
|
||||||
|
|
||||||
|
Your answer should discuss concepts such as:
|
||||||
|
|
||||||
|
Context switching
|
||||||
|
Contention
|
||||||
|
Cache coherence
|
||||||
|
Synchronization overhead
|
||||||
|
|
||||||
|
### ⚠️ Deadlocks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**6 -** Deadlocks often only appear in production, not during testing.
|
||||||
|
|
||||||
|
Explain why this might happen from a thread-scheduling perspective.
|
||||||
|
|
||||||
|
Describe two strategies a developer can use to increase the likelihood of exposing deadlocks during testing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 Practical Project
|
||||||
|
|
||||||
|
## 🏦 Advanced Banking & Concurrent Transaction System
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧭 Overview
|
||||||
|
|
||||||
|
In modern banking systems, thousands of transactions are processed concurrently by multiple worker threads. These threads may access shared bank accounts simultaneously, leading to potential race conditions and consistency issues.
|
||||||
|
|
||||||
|
In this assignment, you will implement the **core concurrency control layer** of a banking simulation system.
|
||||||
|
|
||||||
|
Your goal is to ensure:
|
||||||
|
|
||||||
|
* Correctness under concurrent execution
|
||||||
|
* consistent account state under concurrent execution
|
||||||
|
* Deadlock-free transfer operations
|
||||||
|
* Reasonable concurrency and scalability
|
||||||
|
|
||||||
|
Students are expected to design their own synchronization strategy from scratch.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📦 Provided System (DO NOT MODIFY)
|
||||||
|
|
||||||
|
The following components are fully implemented and must not be changed:
|
||||||
|
|
||||||
|
#### ✅ Infrastructure Layer
|
||||||
|
|
||||||
|
* Transaction generation and models
|
||||||
|
* TransactionProcessor (dispatch layer)
|
||||||
|
* BankingSystem (task submission layer)
|
||||||
|
* ExecutorService configuration
|
||||||
|
* Worker thread execution model
|
||||||
|
* DemoApplication (system runner)
|
||||||
|
* Live monitoring (debug tool)
|
||||||
|
* JUnit test suite
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❗ Your Responsibility (IMPORTANT)
|
||||||
|
|
||||||
|
You are ONLY responsible for implementing thread-safe logic inside:
|
||||||
|
|
||||||
|
#### 📄 `BankAccount.java`
|
||||||
|
|
||||||
|
You must implement:
|
||||||
|
|
||||||
|
```java
|
||||||
|
deposit(long amount);
|
||||||
|
withdraw(long amount);
|
||||||
|
transfer(BankAccount target, long amount);
|
||||||
|
getBalance();
|
||||||
|
```
|
||||||
|
|
||||||
|
Additionally, you may introduce internal synchronization design choices such as:
|
||||||
|
|
||||||
|
* Locks
|
||||||
|
* ReadWriteLock
|
||||||
|
* Atomic variables
|
||||||
|
* Custom ordering strategies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏗 System Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Transaction Stream
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
TransactionProcessor
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
BankingSystem (task submission via ExecutorService)
|
||||||
|
│
|
||||||
|
┌──────────────┼──────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
Worker A Worker B Worker C
|
||||||
|
│ │ │
|
||||||
|
└──────────────┼──────────────┘
|
||||||
|
▼
|
||||||
|
Shared Bank Accounts
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple worker threads may operate on the same account simultaneously.
|
||||||
|
|
||||||
|
Execution order is **non-deterministic**, and correctness must hold for all possible schedules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧠 Core Design Requirement
|
||||||
|
|
||||||
|
Your solution must guarantee:
|
||||||
|
|
||||||
|
* No race conditions
|
||||||
|
* No lost updates
|
||||||
|
* Atomic multi-step operations
|
||||||
|
* Deadlock-free execution under all conditions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Implementation Phases
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🟢 Phase 1 — Thread-Safe Account State
|
||||||
|
|
||||||
|
Implement safe access and mutation for account balance:
|
||||||
|
|
||||||
|
#### Methods:
|
||||||
|
|
||||||
|
* `getBalance()`
|
||||||
|
* `deposit()`
|
||||||
|
* `withdraw()`
|
||||||
|
|
||||||
|
#### Requirements:
|
||||||
|
|
||||||
|
* No race conditions
|
||||||
|
* Reads must never observe invalid intermediate state
|
||||||
|
* Multiple accounts must remain independently concurrent
|
||||||
|
* Negative balances are allowed unless explicitly handled by your implementation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔵 Phase 2 — Atomic Transfers
|
||||||
|
|
||||||
|
Implement:
|
||||||
|
|
||||||
|
```java
|
||||||
|
transfer(BankAccount target, long amount);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Requirements:
|
||||||
|
|
||||||
|
* Transfer must be **fully atomic**
|
||||||
|
* No partial updates allowed
|
||||||
|
* Money must never be lost or created
|
||||||
|
* Consistency must hold under concurrent transfers
|
||||||
|
|
||||||
|
#### Important:
|
||||||
|
|
||||||
|
A transfer involves **two shared resources (accounts)**, which introduces synchronization complexity beyond simple locking.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔴 Phase 3 — Deadlock-Free Design
|
||||||
|
|
||||||
|
Concurrent transfers may create cyclic locking scenarios:
|
||||||
|
|
||||||
|
```
|
||||||
|
Thread 1: A → B
|
||||||
|
Thread 2: B → A
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Requirements:
|
||||||
|
|
||||||
|
* System must be completely deadlock-free
|
||||||
|
* Must pass high-concurrency stress tests
|
||||||
|
* Must remain correct under arbitrary execution ordering
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚙ Allowed Concurrency Tools
|
||||||
|
|
||||||
|
You may use:
|
||||||
|
|
||||||
|
* `synchronized`
|
||||||
|
* `ReentrantLock`
|
||||||
|
* `ReentrantReadWriteLock`
|
||||||
|
* `Condition`
|
||||||
|
* `Atomic classes`
|
||||||
|
* `java.util.concurrent` utilities
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ Restrictions
|
||||||
|
|
||||||
|
You are NOT allowed to:
|
||||||
|
|
||||||
|
* Use busy waiting (e.g., `while(true)`)
|
||||||
|
* Modify test files
|
||||||
|
* Modify method signatures
|
||||||
|
* Change system architecture outside `BankAccount`
|
||||||
|
* Create additional worker threads
|
||||||
|
* Modify transaction processing pipeline
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📊 Live Monitoring (Debug Support)
|
||||||
|
|
||||||
|
A monitoring system is included to display real-time account balances.
|
||||||
|
|
||||||
|
It helps you:
|
||||||
|
|
||||||
|
* Observe race conditions
|
||||||
|
* Debug concurrency issues
|
||||||
|
* Validate correctness under load
|
||||||
|
|
||||||
|
⚠ This component is NOT part of grading and may produce non-deterministic output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🧪 Testing
|
||||||
|
|
||||||
|
A full JUnit test suite validates your solution.
|
||||||
|
|
||||||
|
Your implementation will be evaluated on:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### ✅ Correctness
|
||||||
|
|
||||||
|
* No race conditions
|
||||||
|
* No lost updates
|
||||||
|
* Correct final balances
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 🧨 Robustness
|
||||||
|
|
||||||
|
* No deadlocks under stress tests
|
||||||
|
* Stable execution under high concurrency
|
||||||
|
* Correct behavior under unpredictable scheduling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### ⚡ Performance
|
||||||
|
|
||||||
|
* Independent accounts should not block each other
|
||||||
|
* Avoid unnecessary global locking
|
||||||
|
* Maintain scalable concurrency
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🌟 Bonus Challenge (Optional)
|
||||||
|
|
||||||
|
Implement **conditional waiting for insufficient funds**:
|
||||||
|
|
||||||
|
* `withdraw()` waits until balance is sufficient
|
||||||
|
* `transfer()` waits until source has enough funds
|
||||||
|
|
||||||
|
#### Requirements:
|
||||||
|
|
||||||
|
* No busy waiting
|
||||||
|
* No CPU spinning
|
||||||
|
* No starvation
|
||||||
|
* Must remain thread-safe
|
||||||
|
|
||||||
|
⚠ **Note on Bonus:** The provided JUnit test suite assumes the base behavior (allowing balances to proceed without waiting). Implementing the bonus challenge might cause some base stress tests to time out due to threads waiting indefinitely for funds. It is recommended to verify the bonus logic using your own custom test scenarios.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 💡 Final Hint
|
||||||
|
|
||||||
|
> A correct solution is always more valuable than an optimized incorrect one.
|
||||||
|
|
||||||
|
Start simple, ensure correctness, then improve concurrency and performance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Submission ⌛
|
||||||
|
|
||||||
|
1. Add your mentor as a collaborator or reviewer on the repository.
|
||||||
|
2. Create a `develop` branch (from `main`) for implementing features.
|
||||||
|
3. Use Git for regular commits with meaningful commit messages.
|
||||||
|
4. Push your code and the answers file (Answers.md) to the remote repository.
|
||||||
|
5. Submit a pull request to merge the `develop` branch into `main`.
|
||||||
|
|
||||||
|
**Deadline:** Friday, June 12 (22nd of Khordad)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>dev</groupId>
|
||||||
|
<artifactId>HW-09-Advanced-Multithreading</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>23</maven.compiler.source>
|
||||||
|
<maven.compiler.target>23</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>5.12.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.5.3</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package dev.banking;
|
||||||
|
|
||||||
|
import dev.banking.model.*;
|
||||||
|
import dev.banking.monitor.LiveMonitor;
|
||||||
|
import dev.banking.processor.TransactionProcessor;
|
||||||
|
import dev.banking.service.BankingSystem;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DemoApplication is responsible for:
|
||||||
|
* - Building the system
|
||||||
|
* - Running the simulation
|
||||||
|
* - Managing lifecycle (threads, schedulers)
|
||||||
|
*/
|
||||||
|
public final class DemoApplication {
|
||||||
|
|
||||||
|
private DemoApplication() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void run() {
|
||||||
|
|
||||||
|
System.out.println("Initializing Banking Simulation...\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 1. Create bank accounts
|
||||||
|
*/
|
||||||
|
BankAccount acc1 = new BankAccount(1, 1000);
|
||||||
|
BankAccount acc2 = new BankAccount(2, 2000);
|
||||||
|
BankAccount acc3 = new BankAccount(3, 1500);
|
||||||
|
|
||||||
|
Map<Integer, BankAccount> accounts = new HashMap<>();
|
||||||
|
accounts.put(1, acc1);
|
||||||
|
accounts.put(2, acc2);
|
||||||
|
accounts.put(3, acc3);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 2. Create transactions
|
||||||
|
*/
|
||||||
|
List<Transaction> transactions = List.of(
|
||||||
|
new DepositTransaction(1, 200),
|
||||||
|
new WithdrawTransaction(2, 300),
|
||||||
|
new TransferTransaction(1, 2, 150),
|
||||||
|
new TransferTransaction(2, 3, 400),
|
||||||
|
new DepositTransaction(3, 500),
|
||||||
|
new WithdrawTransaction(1, 100),
|
||||||
|
new TransferTransaction(3, 1, 250)
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 3. Thread pool (workers)
|
||||||
|
*/
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(4);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 4. Processor
|
||||||
|
*/
|
||||||
|
TransactionProcessor processor = new TransactionProcessor(accounts);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 5. Banking system
|
||||||
|
*/
|
||||||
|
BankingSystem bankingSystem = new BankingSystem(executor, processor);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 6. Live monitor (UI simulation)
|
||||||
|
*/
|
||||||
|
LiveMonitor monitor = new LiveMonitor();
|
||||||
|
|
||||||
|
ScheduledExecutorService monitorExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
|
monitorExecutor.scheduleAtFixedRate(() -> {
|
||||||
|
monitor.update(accounts.values());
|
||||||
|
System.out.println("----------------------");
|
||||||
|
}, 0, 1, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 7. Run simulation
|
||||||
|
*/
|
||||||
|
bankingSystem.processTransactions(transactions);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 8. Shutdown / lifecycle management
|
||||||
|
*/
|
||||||
|
shutdown(executor, monitorExecutor);
|
||||||
|
|
||||||
|
System.out.println("\nSimulation completed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void shutdown(
|
||||||
|
ExecutorService executor,
|
||||||
|
ScheduledExecutorService monitorExecutor
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
executor.shutdown();
|
||||||
|
executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
monitorExecutor.shutdown();
|
||||||
|
monitorExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package dev.banking;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point of the application.
|
||||||
|
*
|
||||||
|
* IMPORTANT:
|
||||||
|
* - This is only a wrapper for running the demo.
|
||||||
|
*/
|
||||||
|
public final class Main {
|
||||||
|
|
||||||
|
private Main() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
System.out.println("=================================");
|
||||||
|
System.out.println(" Advanced Banking System");
|
||||||
|
System.out.println("=================================\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
DemoApplication.run();
|
||||||
|
|
||||||
|
System.out.println("\n=================================");
|
||||||
|
System.out.println(" System finished successfully ");
|
||||||
|
System.out.println("=================================");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
System.err.println("Unexpected error occurred:");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package dev.banking.model;
|
||||||
|
|
||||||
|
public class BankAccount {
|
||||||
|
|
||||||
|
private final int accountId;
|
||||||
|
private long balance;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Students may introduce additional fields
|
||||||
|
* such as:
|
||||||
|
* - Lock / ReentrantLock
|
||||||
|
* - ReadWriteLock
|
||||||
|
* - Object monitor
|
||||||
|
* - etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public BankAccount(int accountId, long initialBalance) {
|
||||||
|
this.accountId = accountId;
|
||||||
|
this.balance = initialBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAccountId() {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO:
|
||||||
|
* Increase balance atomically.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - Must not lose updates under concurrency
|
||||||
|
*/
|
||||||
|
public void deposit(long amount) {
|
||||||
|
throw new UnsupportedOperationException("TODO: implement thread-safe deposit");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package dev.banking.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a deposit operation.
|
||||||
|
*/
|
||||||
|
public final class DepositTransaction extends Transaction {
|
||||||
|
|
||||||
|
private final int accountId;
|
||||||
|
|
||||||
|
public DepositTransaction(
|
||||||
|
int accountId,
|
||||||
|
int amount
|
||||||
|
) {
|
||||||
|
super(amount);
|
||||||
|
|
||||||
|
if (accountId < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Invalid account id."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.accountId = accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAccountId() {
|
||||||
|
return accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "DepositTransaction{" +
|
||||||
|
"accountId=" + accountId +
|
||||||
|
", amount=" + getAmount() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package dev.banking.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all transaction types.
|
||||||
|
*/
|
||||||
|
public abstract class Transaction {
|
||||||
|
|
||||||
|
private final int amount;
|
||||||
|
|
||||||
|
protected Transaction(int amount) {
|
||||||
|
|
||||||
|
if (amount <= 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Transaction amount must be positive."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAmount() {
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package dev.banking.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a transfer operation between two accounts.
|
||||||
|
*/
|
||||||
|
public final class TransferTransaction
|
||||||
|
extends Transaction {
|
||||||
|
|
||||||
|
private final int sourceAccountId;
|
||||||
|
private final int targetAccountId;
|
||||||
|
|
||||||
|
public TransferTransaction(
|
||||||
|
int sourceAccountId,
|
||||||
|
int targetAccountId,
|
||||||
|
int amount
|
||||||
|
) {
|
||||||
|
super(amount);
|
||||||
|
|
||||||
|
if (sourceAccountId < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Invalid source account id."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetAccountId < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Invalid target account id."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceAccountId == targetAccountId) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Source and target accounts must be different."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sourceAccountId = sourceAccountId;
|
||||||
|
this.targetAccountId = targetAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSourceAccountId() {
|
||||||
|
return sourceAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTargetAccountId() {
|
||||||
|
return targetAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TransferTransaction{" +
|
||||||
|
"sourceAccountId=" + sourceAccountId +
|
||||||
|
", targetAccountId=" + targetAccountId +
|
||||||
|
", amount=" + getAmount() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package dev.banking.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a withdrawal operation.
|
||||||
|
*/
|
||||||
|
public final class WithdrawTransaction extends Transaction {
|
||||||
|
|
||||||
|
private final int accountId;
|
||||||
|
|
||||||
|
public WithdrawTransaction(
|
||||||
|
int accountId,
|
||||||
|
int amount
|
||||||
|
) {
|
||||||
|
super(amount);
|
||||||
|
|
||||||
|
if (accountId < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Invalid account id."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.accountId = accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAccountId() {
|
||||||
|
return accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "WithdrawTransaction{" +
|
||||||
|
"accountId=" + accountId +
|
||||||
|
", amount=" + getAmount() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package dev.banking.monitor;
|
||||||
|
|
||||||
|
import dev.banking.model.BankAccount;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class LiveMonitor {
|
||||||
|
|
||||||
|
public void update(
|
||||||
|
Collection<BankAccount> accounts
|
||||||
|
) {
|
||||||
|
|
||||||
|
for (BankAccount account : accounts) {
|
||||||
|
|
||||||
|
System.out.printf(
|
||||||
|
"Account %d -> %d%n",
|
||||||
|
account.getAccountId(),
|
||||||
|
account.getBalance()
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package dev.banking.processor;
|
||||||
|
|
||||||
|
import dev.banking.model.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class TransactionProcessor {
|
||||||
|
|
||||||
|
private final Map<Integer, BankAccount> accounts;
|
||||||
|
|
||||||
|
public TransactionProcessor(
|
||||||
|
Map<Integer, BankAccount> accounts
|
||||||
|
) {
|
||||||
|
this.accounts = accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unknown transaction type: " + tx.getClass()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
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;
|
||||||
|
private final TransactionProcessor processor;
|
||||||
|
|
||||||
|
public BankingSystem(
|
||||||
|
ExecutorService executor,
|
||||||
|
TransactionProcessor processor
|
||||||
|
) {
|
||||||
|
this.executor = executor;
|
||||||
|
this.processor = processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processTransactions(
|
||||||
|
List<Transaction> transactions
|
||||||
|
) {
|
||||||
|
|
||||||
|
for (Transaction tx : transactions) {
|
||||||
|
|
||||||
|
executor.submit(() -> {
|
||||||
|
processor.process(tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package banking;
|
||||||
|
|
||||||
|
import dev.banking.model.BankAccount;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class BankAccountConcurrentDepositTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void concurrentDeposits() throws Exception {
|
||||||
|
|
||||||
|
BankAccount account = new BankAccount(1, 0);
|
||||||
|
|
||||||
|
int threads = 100;
|
||||||
|
int perThread = 1000;
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(threads);
|
||||||
|
CountDownLatch latch = new CountDownLatch(threads);
|
||||||
|
|
||||||
|
for (int i = 0; i < threads; i++) {
|
||||||
|
executor.submit(() -> {
|
||||||
|
for (int j = 0; j < perThread; j++) {
|
||||||
|
account.deposit(1);
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
latch.await();
|
||||||
|
executor.shutdown();
|
||||||
|
|
||||||
|
assertEquals(threads * perThread, account.getBalance());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package banking;
|
||||||
|
|
||||||
|
import dev.banking.model.BankAccount;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class BankAccountDepositWithdrawRaceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void concurrentDepositWithdraw() throws Exception {
|
||||||
|
|
||||||
|
BankAccount account = new BankAccount(1, 1_000_000);
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(100);
|
||||||
|
CountDownLatch latch = new CountDownLatch(100);
|
||||||
|
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
|
||||||
|
executor.submit(() -> {
|
||||||
|
for (int j = 0; j < 10_000; j++) {
|
||||||
|
account.deposit(1);
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
executor.submit(() -> {
|
||||||
|
for (int j = 0; j < 10_000; j++) {
|
||||||
|
account.withdraw(1);
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
latch.await();
|
||||||
|
executor.shutdown();
|
||||||
|
|
||||||
|
assertEquals(1_000_000, account.getBalance());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package banking;
|
||||||
|
|
||||||
|
import dev.banking.model.BankAccount;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class BankAccountTransferConsistencyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void transferConservation() throws Exception {
|
||||||
|
|
||||||
|
BankAccount a = new BankAccount(1, 100_000);
|
||||||
|
BankAccount b = new BankAccount(2, 100_000);
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(50);
|
||||||
|
CountDownLatch latch = new CountDownLatch(100);
|
||||||
|
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
|
||||||
|
executor.submit(() -> {
|
||||||
|
for (int j = 0; j < 1000; j++) {
|
||||||
|
a.transfer(b, 1);
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
executor.submit(() -> {
|
||||||
|
for (int j = 0; j < 1000; j++) {
|
||||||
|
b.transfer(a, 1);
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
latch.await();
|
||||||
|
executor.shutdown();
|
||||||
|
|
||||||
|
long total = a.getBalance() + b.getBalance();
|
||||||
|
|
||||||
|
assertEquals(200_000, total);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package banking;
|
||||||
|
|
||||||
|
import dev.banking.model.BankAccount;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class BankAccountTransferDeadlockTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deadlockFreeTransfers() {
|
||||||
|
|
||||||
|
assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {
|
||||||
|
|
||||||
|
BankAccount a = new BankAccount(1, 1_000_000);
|
||||||
|
BankAccount b = new BankAccount(2, 1_000_000);
|
||||||
|
|
||||||
|
int taskCount = 50_000;
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(50);
|
||||||
|
|
||||||
|
CountDownLatch startGun = new CountDownLatch(1);
|
||||||
|
CountDownLatch finishLine = new CountDownLatch(taskCount * 2);
|
||||||
|
|
||||||
|
for (int i = 0; i < taskCount; i++) {
|
||||||
|
|
||||||
|
executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
startGun.await();
|
||||||
|
a.transfer(b, 1);
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} finally {
|
||||||
|
finishLine.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
startGun.await();
|
||||||
|
b.transfer(a, 1);
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} finally {
|
||||||
|
finishLine.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startGun.countDown();
|
||||||
|
|
||||||
|
boolean completed;
|
||||||
|
|
||||||
|
try {
|
||||||
|
completed = finishLine.await(5, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
|
||||||
|
assertTrue(completed, "Deadlock detected: tasks did not complete in time");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package banking;
|
||||||
|
|
||||||
|
import dev.banking.model.*;
|
||||||
|
import dev.banking.processor.TransactionProcessor;
|
||||||
|
import dev.banking.service.BankingSystem;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class BankingSystemEndToEndStressTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fullSystemStressTest() throws Exception {
|
||||||
|
|
||||||
|
BankAccount a = new BankAccount(1, 1_000_000);
|
||||||
|
BankAccount b = new BankAccount(2, 1_000_000);
|
||||||
|
|
||||||
|
Map<Integer, BankAccount> accounts = new HashMap<>();
|
||||||
|
accounts.put(1, a);
|
||||||
|
accounts.put(2, b);
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(8);
|
||||||
|
TransactionProcessor processor = new TransactionProcessor(accounts);
|
||||||
|
BankingSystem system = new BankingSystem(executor, processor);
|
||||||
|
|
||||||
|
List<Transaction> txs = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < 50_000; i++) {
|
||||||
|
txs.add(new TransferTransaction(1, 2, 1));
|
||||||
|
txs.add(new TransferTransaction(2, 1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
system.processTransactions(txs);
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
|
||||||
|
assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package banking;
|
||||||
|
|
||||||
|
import dev.banking.model.*;
|
||||||
|
import dev.banking.processor.TransactionProcessor;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|
||||||
|
public class TransactionProcessorMixedTransactionIntegrationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mixedTransactionsShouldExecuteCorrectly() {
|
||||||
|
|
||||||
|
BankAccount a = new BankAccount(1, 1000);
|
||||||
|
BankAccount b = new BankAccount(2, 2000);
|
||||||
|
BankAccount c = new BankAccount(3, 1500);
|
||||||
|
|
||||||
|
Map<Integer, BankAccount> accounts = new HashMap<>();
|
||||||
|
accounts.put(1, a);
|
||||||
|
accounts.put(2, b);
|
||||||
|
accounts.put(3, c);
|
||||||
|
|
||||||
|
TransactionProcessor processor = new TransactionProcessor(accounts);
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(8);
|
||||||
|
|
||||||
|
List<Transaction> txs = List.of(
|
||||||
|
new DepositTransaction(1, 100),
|
||||||
|
new WithdrawTransaction(2, 50),
|
||||||
|
new TransferTransaction(1, 2, 30),
|
||||||
|
new TransferTransaction(2, 3, 70),
|
||||||
|
new DepositTransaction(3, 200)
|
||||||
|
);
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
for (Transaction tx : txs) {
|
||||||
|
executor.submit(() -> processor.process(tx));
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user