Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39b3b5b466 |
+148
@@ -0,0 +1,148 @@
|
|||||||
|
1 - What are atomic variables?
|
||||||
|
|
||||||
|
Atomic variables are variables that support thread-safe operations without using explicit locks.
|
||||||
|
|
||||||
|
They ensure that operations such as incrementing, decrementing, or updating a value are performed as a single indivisible action.
|
||||||
|
|
||||||
|
Difference from ordinary variables:
|
||||||
|
|
||||||
|
Ordinary variable: Multiple threads can modify it simultaneously, causing race conditions.
|
||||||
|
Atomic variable: Operations are performed atomically, preventing race conditions for single-variable updates.
|
||||||
|
|
||||||
|
2 - Name at least four classes from the java.util.concurrent.atomic package that provide atomic operations for different data types.
|
||||||
|
|
||||||
|
AtomicInteger
|
||||||
|
AtomicLong
|
||||||
|
AtomicBoolean
|
||||||
|
AtomicReference<T>
|
||||||
|
|
||||||
|
Typical use case for AtomicInteger:
|
||||||
|
AtomicInteger is commonly used as a thread-safe counter when multiple threads need to increment a shared value concurrently.
|
||||||
|
|
||||||
|
3 - Compare locks with atomic variables.
|
||||||
|
|
||||||
|
atomic variables:
|
||||||
|
|
||||||
|
Advantages:
|
||||||
|
|
||||||
|
Faster than locks for simple operations.
|
||||||
|
Non-blocking.
|
||||||
|
Lower overhead.
|
||||||
|
|
||||||
|
Best for:
|
||||||
|
|
||||||
|
Counters.
|
||||||
|
Flags.
|
||||||
|
Single-variable updates.
|
||||||
|
Locks
|
||||||
|
|
||||||
|
locks:
|
||||||
|
|
||||||
|
Advantages:
|
||||||
|
Can protect multiple variables together.
|
||||||
|
Support complex critical sections.
|
||||||
|
|
||||||
|
Best for:
|
||||||
|
|
||||||
|
Operations involving multiple shared objects.
|
||||||
|
Complex business logic requiring mutual exclusion.
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
|
||||||
|
Use atomic variables for simple thread-safe updates.
|
||||||
|
Use locks when multiple operations must be performed as one atomic unit.
|
||||||
|
|
||||||
|
Bonus Task
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class AtomicDemo
|
||||||
|
{
|
||||||
|
static int normalCounter = 0;
|
||||||
|
static AtomicInteger atomicCounter = new AtomicInteger(0);
|
||||||
|
|
||||||
|
public static void main(String[] args) throws InterruptedException
|
||||||
|
{
|
||||||
|
|
||||||
|
int numThreads = 10;
|
||||||
|
int incrementsPerThread = 100000;
|
||||||
|
|
||||||
|
Thread[] threads = new Thread[numThreads];
|
||||||
|
|
||||||
|
for (int i = 0; i < numThreads; i++) {
|
||||||
|
threads[i] = new Thread(() -> {
|
||||||
|
for (int j = 0; j < incrementsPerThread; j++) {
|
||||||
|
normalCounter++;
|
||||||
|
atomicCounter.incrementAndGet();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
threads[i].start();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("Normal Counter: " + normalCounter);
|
||||||
|
System.out.println("Atomic Counter: " + atomicCounter.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected Result:
|
||||||
|
|
||||||
|
Normal Counter: 823451 (varies)
|
||||||
|
Atomic Counter: 1000000
|
||||||
|
|
||||||
|
The normal counter may be incorrect because multiple threads overwrite each other's updates. The atomic counter always produces the correct result.
|
||||||
|
|
||||||
|
4 - A program is completely free of race conditions but still performs poorly under high contention.
|
||||||
|
Explain how this situation can occur.
|
||||||
|
|
||||||
|
A program can be completely correct yet still perform poorly because threads spend too much time waiting for shared resources.
|
||||||
|
|
||||||
|
Three factors that limit scalability:
|
||||||
|
|
||||||
|
Lock Contention
|
||||||
|
Many threads compete for the same lock and must wait.
|
||||||
|
|
||||||
|
Synchronization Overhead
|
||||||
|
Acquiring and releasing locks consumes CPU time.
|
||||||
|
|
||||||
|
Thread Blocking
|
||||||
|
Threads frequently pause while waiting for locks or resources, reducing parallelism.
|
||||||
|
|
||||||
|
Correctness is guaranteed, but throughput decreases as contention increases.
|
||||||
|
|
||||||
|
5 - Many concurrent systems experience performance degradation as the number of threads increases.
|
||||||
|
Explain why adding more threads does not always improve performance.
|
||||||
|
|
||||||
|
The CPU must save and restore thread state when switching between threads. Too many threads increase this overhead.
|
||||||
|
|
||||||
|
Contention
|
||||||
|
More threads compete for the same locks and resources, causing waiting.
|
||||||
|
|
||||||
|
Cache Coherence
|
||||||
|
When multiple CPUs modify shared data, cache contents must be synchronized, creating extra communication overhead.
|
||||||
|
|
||||||
|
Synchronization Overhead
|
||||||
|
Locks, atomic operations, and coordination mechanisms consume time and reduce the benefits of parallel execution.
|
||||||
|
|
||||||
|
Conclusion: After a certain point, adding more threads increases overhead more than useful work, causing performance to stagnate or even decrease.
|
||||||
|
|
||||||
|
6 - Deadlocks often only appear in production, not during testing.Explain why this might happen from a thread-scheduling perspective.
|
||||||
|
|
||||||
|
Deadlocks depend on the exact timing and scheduling of threads.
|
||||||
|
During testing, threads may execute in a favorable order and never enter the deadlock state. In production, different workloads, hardware, and timing conditions can cause the problematic scheduling sequence to occur.
|
||||||
|
|
||||||
|
Two ways to expose deadlocks during testing
|
||||||
|
|
||||||
|
1. Increase concurrency
|
||||||
|
Run with many threads.execute tests repeatedly under heavy load.
|
||||||
|
|
||||||
|
|
||||||
|
2. Introduce timing variations
|
||||||
|
Add random delays (Thread.sleep()).
|
||||||
|
Use stress-testing tools to force different thread interleavings.
|
||||||
|
|
||||||
|
These techniques increase the chance that threads acquire locks in a problematic order, revealing deadlocks before deployment.
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
package dev.banking.model;
|
package dev.banking.model;
|
||||||
|
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
public class BankAccount {
|
public class BankAccount {
|
||||||
|
|
||||||
private final int accountId;
|
private final int accountId;
|
||||||
@@ -13,6 +16,7 @@ public class BankAccount {
|
|||||||
* - Object monitor
|
* - Object monitor
|
||||||
* - etc.
|
* - etc.
|
||||||
*/
|
*/
|
||||||
|
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
|
|
||||||
public BankAccount(int accountId, long initialBalance) {
|
public BankAccount(int accountId, long initialBalance) {
|
||||||
this.accountId = accountId;
|
this.accountId = accountId;
|
||||||
@@ -23,56 +27,96 @@ public class BankAccount {
|
|||||||
return accountId;
|
return accountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* TODO:
|
* Thread-safe balance read.
|
||||||
* 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.readLock().lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return balance;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lock.readLock().unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* TODO:
|
* Thread-safe deposit.
|
||||||
* 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.writeLock().lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
balance += amount;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* TODO:
|
* Thread-safe withdrawal.
|
||||||
* 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.writeLock().lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
balance -= amount;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lock.writeLock().unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* TODO:
|
* Atomic deadlock-free transfer.
|
||||||
* Transfer money between two accounts atomically.
|
|
||||||
*
|
*
|
||||||
* IMPORTANT REQUIREMENTS:
|
* Lock ordering rule:
|
||||||
* - Must be atomic (no partial transfer)
|
* Always acquire the lock of the account with the
|
||||||
* - Must be deadlock-free
|
* smaller accountId first.
|
||||||
* - 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");
|
|
||||||
|
if (target == null)
|
||||||
|
throw new IllegalArgumentException("Target account cannot be null.");
|
||||||
|
|
||||||
|
if (this == target)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BankAccount first = this.accountId < target.accountId ? this : target;
|
||||||
|
|
||||||
|
BankAccount second = this.accountId < target.accountId ? target : this;
|
||||||
|
|
||||||
|
first.lock.writeLock().lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
second.lock.writeLock().lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.balance -= amount;
|
||||||
|
target.balance += amount;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
second.lock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
first.lock.writeLock().unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user