3 Commits
Author SHA1 Message Date
Fateme_Azizi ed4473a239 implement BankAccount.java 2026-06-15 17:12:58 +03:30
Fateme_Azizi 69620f3e55 implement Answers.md 2026-06-15 02:32:34 +03:30
Fateme_Azizi b4af6880fe add Answers.md 2026-06-14 20:14:13 +03:30
2 changed files with 144 additions and 16 deletions
+97
View File
@@ -0,0 +1,97 @@
## Question 1
An **atomic variable** is a special type of variable that performs operations
The purpose of using them is to prevent **race conditions**
- **Atomic variables**: thread-safe for simple operations (like increment, update).
- **Ordinary variables**: can cause race conditions if multiple threads change them together without protection.
## Question 2
Examples of classes in java.util.concurrent.atomic:
1. AtomicInteger
2. AtomicLong
3. AtomicBoolean
4. AtomicReference
AtomicInteger:
Used as a thread-safe counter. For example, in a multithreaded server, multiple threads can increase the number of requests safely without using synchronized.
## Question 3
Atomic variables: It's better for simple operations on a single value (increment, update, compare-and-set).
They are usually faster and lighter because they avoid blocking threads.
Locks: Used when multiple operations must happen together. Threads wait until the lock is released.
Use a lock when:
- Multiple variables must stay consistent together
- A complex sequence of actions must be protected
- You need critical sections with more control
Use an atomic variable when:
- Only one variable is shared
- Operations are simple (counter)
- High performance and low overhead are important
## Question 4
A program can be free of race conditions but still be slow under high contention because correctness does not mean good performance.
Threads may work safely but spend time waiting instead of doing useful work.
Three factors that limit scalability:
1. Lock Contention
Many threads try to access the same lock. Only one can continue, so others wait, reducing parallelism.
2. Context Switching
The CPU constantly switches between threads. Frequent switching adds overhead and wastes time.
3. Synchronization Overhead
Operations like locks, atomic updates, and coordination between threads require extra CPU work and memory synchronization.
## Question 5
Adding more threads does not always improve performance because after a certain point, threads start spending more time coordinating and competing than doing actual work.
1. When there are too many threads, the CPU frequently switches between them.
Saving and restoring thread states creates overhead and reduces efficiency.
2. Multiple threads may compete for the same resources (locks, memory, shared data). Some threads must wait, so parallel execution decreases.
3. Each CPU core has its own cache. When several threads modify shared data, caches must stay synchronized,
creating extra communication and slowing execution.
4. Locks, atomic operations, and coordination mechanisms require additional processing time to maintain correctness.
## Question 6
Deadlocks often appear in production but not during testing. During testing, threads may run in a favorable order and
never reach the lock sequence that causes deadlock. In production, different timing, higher load, and more threads can create the exact conditions needed.
Two strategies to expose deadlocks during testing:
1. Stress / Load Testing
Run the program with many threads, repeated executions, and high concurrency to increase the chance of problems.
2. Introduce Scheduling Variations
Add random delays, sleeps, or use concurrency testing tools to force different execution orders and increase the probability of triggering deadlocks.
@@ -1,9 +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;
private Lock lock = new ReentrantLock();
/*
* Students may introduce additional fields
@@ -23,44 +27,44 @@ public class BankAccount {
return accountId;
}
/*
* TODO:
* Return the current balance in a thread-safe way.
/* 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.
/* Increase balance atomically.
*
* Requirements:
* - Must not lose updates under concurrency
*/
public void deposit(long amount) {
throw new UnsupportedOperationException("TODO: implement thread-safe deposit");
public synchronized void deposit(long amount) {
balance += amount;
}
/*
* TODO:
* Decrease balance atomically.
/* 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");
public synchronized void withdraw(long amount) {
balance -= amount;
}
/*
* TODO:
* Transfer money between two accounts atomically.
*
* IMPORTANT REQUIREMENTS:
@@ -73,6 +77,33 @@ public class BankAccount {
* - Or tryLock with retry strategy
*/
public void transfer(BankAccount target, long amount) {
throw new UnsupportedOperationException("TODO: implement atomic deadlock-free transfer");
BankAccount first, second;
if(target == this) return;
if (this.getAccountId() < target.getAccountId()) {
first = this;
second = target;
}
else {
first = target;
second = this;
}
first.lock.lock();
try {
second.lock.lock();
try {
this.withdraw(amount);
target.deposit(amount);
}
finally {
second.lock.unlock();
}
}
finally {
first.lock.unlock();
}
}
}