Merge pull request 'HW9' (#1) from develop into main

Reviewed-on: #1

100 / 100 * 0.7 delay
This commit was merged in pull request #1.
This commit is contained in:
2026-07-04 07:48:46 +00:00
8 changed files with 146 additions and 50 deletions
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+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://repo.maven.apache.org/maven2" />
</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" languageLevel="JDK_25" default="true" project-jdk-name="ms-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>
+41
View File
@@ -0,0 +1,41 @@
1 - What are atomic variables?
متغیرهای اتمیک متغیرهایی هستن که عملیات روشون به صورت یکپارچه و تو یه مرحله انجام میشه. یعنی نمیشه وسط کار یه ترد روی این متغیر، یه ترد دیگه بیاد دخالت کنه و کار رو نصفه بذاره.
فرق اصلیشون با متغیرهای معمولی اینه که مثلا تو یه متغیر عادی یه جمع ساده مثل i++ خودش شامل سه مرحله است: خوندن مقدار فعلی، جمع کردنش با یک، و نوشتن مقدار جدید. اگه دو تا ترد همزمان این کار رو بکنن با هم تداخل پیدا میکنن و دیتامون خراب میشه. اما متغیرهای اتمیک با کمک دستورات سطح سخت‌افزار این کار رو بدون نیاز به قفل کردن و کاملا امن انجام میدن.
2 - Classes from java.util.concurrent.atomic
چهار تا از کلاس‌های معروف این پکیج این موارد هستن:
AtomicInteger
AtomicLong
AtomicBoolean
AtomicReference
مثلا در مورد AtomicInteger، بیشترین کاربردش برای ساختن شمارنده‌های امن هست. فرض کنید میخوایم تعداد بازدیدهای یه سایت رو بشمریم، چون ممکنه همزمان چند تا ترد بخوان این عدد رو ببرن بالا، با استفاده از این کلاس خیالمون راحته که با هم تداخل نمیکنن و هیچ عددی جا نمیفته.
3 - Compare locks with atomic variables
اتمیک‌ها کلا قفلی ندارن و با الگوریتم‌های سخت‌افزاری کار میکنن که باعث میشه تردها الکی متوقف نشن و خیلی سریع کارشون رو بکنن. اما قفل‌ها بدبینانه‌تر عمل میکنن، یعنی وقتی یه ترد قفلی رو میگیره، بقیه تردها باید تو صف منتظر بمونن تا کارش تموم بشه.
اگه فقط با یه متغیر کار داریم قطعا استفاده از اتمیک بهتره چون سربار بلاک شدن تردها رو نداره و خیلی سریع‌تره. اما وقتی عملیات ما پیچیده‌تره و روی چند تا متغیر مختلف تاثیر میذاره (مثل همین پروژه که پول از یه حساب کم میشه و به یه حساب دیگه اضافه میشه)، اینجا دیگه نمیشه فقط به اتمیک اعتماد کرد و حتما باید از قفل استفاده کنیم تا کل عملیات با هم انجام بشه.
4 - High contention and poor performance without race conditions
این اتفاق وقتی میفته که ما از نظر ایمنی کدمون رو کامل ضد ضربه کردیم ولی انقدر همه جا رو قفل کردیم که تردها به جای اینکه موازی کار کنن، تو صف میمونن تا نوبتشون بشه. چند تا دلیل داره که باعث میشه برنامه کند بشه:
اولیش قفل کردن اضافیه. مثلا به جای اینکه فقط همون متغیر یا حساب خاص رو قفل کنیم، کل متد یا یه بخش بزرگی از برنامه رو قفل میکنیم.
دلیل دوم رقابت شدید سر قفله. وقتی همه تردها بخوان مدام به یه منبع مشترک دسترسی پیدا کنن، سیستم زمان زیادی رو هدر میده تا فقط این صف و انتظارهای تردها رو مدیریت کنه.
دلیل سوم هم گرسنگی تردهاست. یعنی یه سری ترد ممکنه هی عقب بیفتن و نتونن قفل رو بگیرن چون تردهای دیگه سریع‌تر قفل رو تصاحب میکنن، این باعث میشه کلا سرعت پردازش بیاد پایین.
5 - Why adding more threads does not always improve performance
اضافه کردن ترد همیشه باعث بهبود سرعت نمیشه چون سخت‌افزار ما ظرفیت محدودی داره. وقتی تعداد تردها خیلی زیاد بشه، سیستم عامل مجبوره مدام بینشون سوییچ کنه که خودش به شدت زمان‌بره.
از طرفی رقابت بالا میره و تردهای بیشتری تو حالت بلاک شده قرار میگیرن. یه مشکل دیگه هم کش پردازنده‌هاست؛ وقتی هسته‌های مختلف میخوان یه متغیر مشترک رو تغییر بدن، دائم باید کش همدیگه رو باطل کنن که این کار پهنای باند مموری رو اشغال میکنه. علاوه بر این، خود مکانیزم قفل کردن و بیدار کردن تردها هم پردازنده رو درگیر میکنه و باعث افت بازدهی میشه.
6 - Why deadlocks appear in production and how to expose them
بن‌بست‌ها خیلی به زمان‌بندی دقیق تردها ربط دارن. ما وقتی داریم تست میکنیم، بار روی سیستم کمه و کارها راحت انجام میشن. ولی تو محیط واقعی به خاطر ترافیک و درخواست‌های زیاد، سیستم عامل مجبوره تردها رو با ترتیب‌های غیرقابل پیش‌بینی متوقف و اجرا کنه. تو این شلوغی احتمال اینکه دو تا ترد دقیقا تو یه لحظه متقاطع منابع هم رو قفل کنن و گیر بیفتن خیلی زیاده.
برای اینکه تو زمان تست بتونیم این باگ‌ها رو پیدا کنیم میشه دو تا کار کرد:
اول اینکه بیایم استرس تست با همروندی بالا بنویسیم. یعنی مثلا صدها ترد رو همزمان بندازیم به جون دو سه تا حساب بانکی تا تراکنش‌های ضربدری انجام بدن و شلوغی سیستم واقعی رو شبیه‌سازی کنیم.
راه دوم اینه که عمدا تو بخش‌های حساس کد یه وقفه چند میلی‌ثانیه‌ای بندازیم تا سیستم عامل مجبور بشه ترد رو متوقف کنه. اینطوری اگه مشکلی تو ترتیب قفل‌گذاری باشه زودتر خودش رو نشون میده.
@@ -1,18 +1,13 @@
package dev.banking.model; package dev.banking.model;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount { public class BankAccount {
private final int accountId; private final int accountId;
private long balance; private long balance;
private final Lock lock = new ReentrantLock();
/*
* Students may introduce additional fields
* such as:
* - 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 +18,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"); lock.lock();
try {
return balance;
} finally {
lock.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"); 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) { 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) { public void transfer(BankAccount target, long amount) {
throw new UnsupportedOperationException("TODO: implement atomic deadlock-free transfer"); BankAccount firstLock = this.accountId < target.getAccountId() ? this : target;
BankAccount secondLock = this.accountId < target.getAccountId() ? target : this;
firstLock.lock.lock();
try {
secondLock.lock.lock();
try {
this.balance -= amount;
target.balance += amount;
} finally {
secondLock.lock.unlock();
}
} finally {
firstLock.lock.unlock();
}
} }
} }