From 0935d99615b8ff388cbac500d283a45e045f383c Mon Sep 17 00:00:00 2001 From: Amir Ali Amiri Date: Sun, 28 Jun 2026 18:09:38 +0330 Subject: [PATCH] Implement thread-safe bank account logic and add answers to theoretical questions --- .idea/.gitignore | 10 + .idea/compiler.xml | 13 + .idea/encodings.xml | 7 + .idea/jarRepositories.xml | 20 + .idea/misc.xml | 12 + .idea/vcs.xml | 6 + README.md | 355 ++---------------- .../java/dev/banking/model/BankAccount.java | 92 +++-- 8 files changed, 155 insertions(+), 360 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -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/ diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..812c3f9 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..abb532a --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0d77bc8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 0d236df..52696f4 100644 --- a/README.md +++ b/README.md @@ -1,343 +1,60 @@ -# Ninth Assignment — Advanced Multithreading +
-![HW09 Image.png](HW09%20Image.png) +# پاسخ به سوالات تئوری — هوم‌وورک نهم ---- -## 📋 Introduction -This assignment is divided into two main sections: +## ⚛️ متغیرهای اتمیک و همگام‌سازی (Atomic Variables & Synchronization) -### **Theoretical Questions**: -- You are asked to answer questions about multithreading concepts. +### ۱. متغیرهای اتمیک چیستند، چه هدفی دارند و تفاوتشان با متغیرهای معمولی چیست؟ +متغیرهای اتمیک کلاس‌هایی در جاوا هستند که به ما اجازه می‌دهند عملیات‌های خواندن و نوشتن (مثل افزایش یا کاهش مقدار) را روی یک متغیر بدون نیاز به قفل‌های سنگین (مثل بلوک‌های `synchronized`)، به صورت کاملاً «یکپارچه و غیرقابل‌تفکیک» یا همان اتمیک انجام دهیم. -### **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. +* **تفاوت با متغیرهای معمولی:** عملیاتی مثل `count++` روی متغیرهای معمولی در ظاهر یک خط است، اما در سطح پردازنده شامل ۳ مرحله است (خواندن مقدار قدیمی، اضافه کردن یک واحد، و نوشتن مقدار جدید). اگر چند ترد همزمان این کار را بکنند، تداخل (Race Condition) پیش می‌آید و دیتا خراب می‌شود. اما متغیرهای اتمیک این عملیات ۳ مرحله‌ای را به کمک سخت‌افزار (دستورات سطح پایین پردازنده مثل CAS - Compare-And-Swap) به یک عملیات واحد و تفکیک‌ناپذیر تبدیل می‌کنند تا بدون بلاک شدن تردها، امنیت داده‌ها حفظ شود. --- -## 🧠 Theoretical Questions +### ۲. نام بردن حداقل ۴ کلاس از پکیج `java.util.concurrent.atomic` و توضیح کاربرد یکی از آن‌ها +چهار کلاس پرکاربرد عبارتند از: +1. `AtomicInteger` +2. `AtomicLong` +3. `AtomicBoolean` +4. `AtomicReference` -### **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 +* **توضیح کاربرد AtomicInteger:** از این کلاس معمولاً برای ساختن شمارنده‌های سراسری (Global Counters) یا تولید شناسه‌های یکتا در برنامه‌های چندنخی استفاده می‌شود. مثلاً اگر یک وب‌سرور داشته باشیم که بخواهد تعداد درخواست‌های ورودی را بشمارد، به جای استفاده از قفل که سرعت را پایین می‌آورد، از متد `incrementAndGet()` در این کلاس استفاده می‌کنیم تا تردها همزمان و با بالاترین سرعت تعداد را بالا ببرند. --- -**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 +### ۳. مقایسه قفل‌ها (Locks) با متغیرهای اتمیک (در چه سناریوهایی کدام‌یک بر دیگری برتری دارد؟) +* **برتری متغیرهای اتمیک:** وقتی که فقط با **یک متغیر مستقل** (مثل یک کانتر یا یک فلگ وضعیت) سر و کار داریم و رقابت بین تردها معمولی یا متوسط است، متغیرهای اتمیک بسیار سریع‌تر هستند چون تردها وارد وضعیت Block نمی‌شوند و هزینه سنگین Context Switching رخ نمی‌دهد. +* **برتری قفل‌ها (Locks):** وقتی که منطق برنامه پیچیده است و شامل **چندین متغیر وابسته به هم** می‌شود (مثلاً در همین پروژه بانک، کسر از یک حساب و واریز به حساب دیگر باید با هم قفل شوند)، متغیرهای اتمیک به تنهایی نمی‌توانند کل این فرآیند چندمرحله‌ای را اتمیک کنند. در این سناریوها و یا زمانی که رقابت بین تردها فوق‌العاده بالاست، استفاده از قفل‌ها (مانند `ReentrantLock` یا `ReadWriteLock`) گزینه بهتری است. --- -**4 -** A program is completely free of race conditions but still performs poorly under high contention. +## 🔒 قفل‌ها و طراحی همزمان (Locks & Concurrent Design) -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 +### ۴. چطور ممکن است برنامه‌ای کاملاً بدون Race Condition باشد اما زیر بار شدید کارایی پایینی داشته باشد؟ (۳ عامل محدودکننده قابلیت مقیاس‌پذیری) +دلیلش این است که برای حل Race Condition ممکن است از قفل‌گذاری‌های بیش از حد بزرگ یا ناشیانه استفاده کرده باشیم. سه عامل اصلی که جلوی مقیاس‌پذیری (Scalability) برنامه را می‌گیرند عبارتند از: +1. **انحصار متقابل بیش از حد (Coarse-Grained Locking):** اگر کل متدهای یک کلاس بزرگ را قفل انحصاری کنیم، در بار شدید تردها مجبور می‌شوند پشت سر هم در یک صف طولانی بایستند و برنامه عملاً از حالت موازی خارج شده و ترتیبی کار می‌کند. +2. **هزینه تعویض کانکشن/ترد (Context Switching Overhead):** وقتی تردهای زیادی برای گرفتن یک قفل تلاش می‌کنند، سیستم‌عامل مدام باید یک ترد را متوقف و ترد دیگری را فعال کند. این جابه‌جایی بین هسته‌های پردازنده، منابع زیادی از CPU را هدر می‌دهد. +3. **محدودیت قانون امدال (Amdahl's Law):** طبق این قانون، سرعت کل برنامه محدود به آن بخشی از کد است که مجبور است به صورت ترتیبی و غیرموازی اجرا شود. هر چقدر بخش‌های دارای قفل انحصاری در برنامه بیشتر باشد، افزایش هسته‌های CPU کمک خاصی به سرعت نخواهد کرد. --- -**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. +### ۵. چرا افزایش تعداد تردها همیشه کارایی را بهتر نمی‌کند؟ +چهار دلیل فنی برای این موضوع وجود دارد: +* **تعویض زمینه (Context Switching):** ساختن تردهای بیشتر از تعداد هسته‌های واقعی پردازنده باعث می‌شود CPU بخش زیادی از زمانش را صرف جابه‌جایی بین تردها و ذخیره/بازیابی وضعیت آن‌ها کند، به جای اینکه کار واقعی انجام دهد. +* **رقابت بر سر منابع (Contention):** وقتی تعداد تردها خیلی زیاد شود، همه برای گرفتن قفل منابع مشترک می‌جنگند و زمان زیادی را در صفِ انتظار قفل تلف می‌کنند. +* **انسجام حافظه نهان (Cache Coherence Overhead):** وقتی تردهای مختلف روی هسته‌های مختلف مدام یک دیتای مشترک را تغییر می‌دهند، پردازنده مجبور است سیگنال‌های مداومی برای به‌روزرسانی حافظه کش بین هسته‌ها بفرستد (Cache Invalidation) که پهنای باند حافظه را به شدت درگیر می‌کند. +* **بار اضافی همگام‌سازی (Synchronization Overhead):** مکانیزم‌های مدیریت قفل در سیستم‌عامل و جاوا خودشان مصرف‌کننده منابع هستند و اجرای دستوراتِ هماهنگی بین تردها، بار پردازشی اضافه به سیستم تحمیل می‌کند. --- -## 💻 Practical Project +## 💀 بن‌بست (Deadlocks) -## 🏦 Advanced Banking & Concurrent Transaction System +### ۶. چرا ددلاک‌ها معمولاً در تست‌ها دیده نمی‌شوند و فقط در محیط پروداکشن (زیر بار واقعی) رخ می‌دهند؟ معرفی ۲ استراتژی برای آشکار کردن آن‌ها. +**چرا در تست‌ها دیده نمی‌شوند؟** +ددلاک برای رخ دادن نیاز به یک توالی زمانی بسیار خاص و نادر (Timing/Interleaving) دارد. در محیط تست‌های معمولی، بار روی سیستم کم است و زمان‌بندی تردها توسط سیستم‌عامل به گونه‌ای جلو می‌رود که تردها معمولاً با هم تلاقی پیدا نمی‌کنند. اما در محیط واقعی (Production)، حجم درخواست‌ها بالا می‌رود و احتمال اینکه دو ترد دقیقاً در یک لحظه به طور متقاطع منابع هم را طلب کنند بالا رفته و ددلاک رخ می‌دهد. ---- +**دو استراتژی برای آشکار کردن ددلاک در طول تست:** +1. **استفاده از تست‌های استرس طولانی‌مدت به همراه آشکارسازها (Thread Dumps):** اجرای تست‌ها با تعداد ترد بالا و حجم دیتای سنگین برای مدت طولانی، و گرفتن Thread Dump به کمک ابزارهایی مثل `jstack` تا متوجه شویم کدام تردها در وضعیت `BLOCKED` روی هم قفل شده‌اند. +2. **استفاده از ابزارهای مانیتورینگ زنده (مثل JConsole یا VisualVM):** این ابزارها بخشی به نام "Detect Deadlock" دارند که به صورت گرافیکی ساختار کدهای در حال اجرا را آنالیز کرده و در صورت وجود حلقه‌های ددلاک سریعاً آن را گزارش می‌دهند. -### 🧭 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) \ No newline at end of file +
\ No newline at end of file diff --git a/src/main/java/dev/banking/model/BankAccount.java b/src/main/java/dev/banking/model/BankAccount.java index 745ede2..fc03d5f 100644 --- a/src/main/java/dev/banking/model/BankAccount.java +++ b/src/main/java/dev/banking/model/BankAccount.java @@ -1,18 +1,14 @@ package dev.banking.model; +import java.util.concurrent.locks.ReentrantReadWriteLock; + public class BankAccount { private final int accountId; private long balance; - /* - * Students may introduce additional fields - * such as: - * - Lock / ReentrantLock - * - ReadWriteLock - * - Object monitor - * - etc. - */ + // تعریف ReadWriteLock برای مدیریت همزمان خواندن و نوشتن بدون بلاک شدن اضافی + private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); public BankAccount(int accountId, long initialBalance) { this.accountId = accountId; @@ -24,55 +20,69 @@ public class BankAccount { } /* - * 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 + * گرفتن موجودی به صورت ترد-ایمن. + * از readLock استفاده کردم تا چندتا ترد بتونن همزمان بدون معطل کردن هم، بالانس رو بخونن. */ public long getBalance() { - throw new UnsupportedOperationException("TODO: implement thread-safe balance read"); + rwLock.readLock().lock(); + try { + return balance; + } finally { + rwLock.readLock().unlock(); + } } /* - * TODO: - * Increase balance atomically. - * - * Requirements: - * - Must not lose updates under concurrency + * واریز پول به حساب به صورت اتمیک. + * اینجا چون مقدار تغییر میکنه حتماً باید writeLock بگیریم تا دیتا خراب نشه. */ public void deposit(long amount) { - throw new UnsupportedOperationException("TODO: implement thread-safe deposit"); + rwLock.writeLock().lock(); + try { + balance += amount; + } finally { + rwLock.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) { - throw new UnsupportedOperationException("TODO: implement thread-safe withdraw"); + rwLock.writeLock().lock(); + try { + balance -= amount; + } finally { + rwLock.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 + * انتقال پول بین دوتا حساب به صورت کاملاً اتمیک و بدون ددلاک. + * برای اینکه ددلاک پیش نیاد، حساب‌ها رو بر اساس accountId مرتب میکنیم و همیشه قفل اونی که آیدی کمتری داره رو اول میگیریم. */ public void transfer(BankAccount target, long amount) { - throw new UnsupportedOperationException("TODO: implement atomic deadlock-free transfer"); + // پیدا کردن ترتیب ثابت برای قفل گذاری بر اساس ID حساب‌ها + BankAccount first = this.accountId < target.getAccountId() ? this : target; + BankAccount second = this.accountId < target.getAccountId() ? target : this; + + // گرفتن قفل حساب اول + first.rwLock.writeLock().lock(); + try { + // گرفتن قفل حساب دوم + second.rwLock.writeLock().lock(); + try { + // حالا که قفل هردو حساب رو داریم، با خیال راحت انتقال رو انجام میدیم + this.balance -= amount; + target.balance += amount; + } finally { + // باز کردن قفل حساب دوم + second.rwLock.writeLock().unlock(); + } + } finally { + // باز کردن قفل حساب اول + first.rwLock.writeLock().unlock(); + } } } \ No newline at end of file